ADR-021: Photo Studio — Go вместо Java и token introspection
Статус
Accepted
Контекст
Появился новый сервис «AI Студия» (Photo Studio Service, gensvc) — рабочий прототип для генерации фотографий блюд через AI. Необходимо интегрировать его в ERP-платформу франшизы.
Сервис отличается от остальных по двум ключевым характеристикам:
Характеристика 1: Язык — Go, не Java.
Платформенный стандарт ERP — Java 21 + Spring Boot 3. Photo Studio Service написан на Go (chi router, pgx, стандартная библиотека). Прототип уже работает в продакшене (https://ai-photo-studio.nirbi.ru).
Характеристика 2: Метод валидации JWT.
Остальные ERP-сервисы используют ADR-001 — локальный JWT-фильтр (HS256 + shared JWT_SECRET). Photo Studio Service в прототипе использует JWKS-валидацию (RS256, внешний ключ). При интеграции нужно выбрать финальный подход.
Проблема 1: Язык реализации
Нужно определить — сохранять Go или переписывать на Java.
Рассмотренные варианты
- Сохранить Go — интегрировать прототип как есть, принять отступление от стандарта
- Переписать на Java — создать новый
erp-photo-studio-serviceна Spring Boot по образцу остальных сервисов - Обёртка-адаптер — Java-сервис как тонкий gateway, Go-движок без REST API
Проблема 2: Валидация JWT
При интеграции Photo Studio Service с Auth Service есть три подхода:
- JWKS-only — сервис сам скачивает публичные ключи с JWKS endpoint Auth Service и валидирует подпись локально. Не требует сетевого вызова на каждый запрос, но не получает актуальный список permissions (они не входят в JWT payload).
- Token Exchange — новая схема: сервис обменивает пользовательский токен на токен со встроенными permissions. Требует нового endpoint в Auth Service, нет в текущем контракте.
- Token Introspection (
POST /internal/auth/validate) — сервис вызывает существующий internal endpoint Auth Service, получаетvalid,user_id,franchise_id,permissions[],scope. Результат кэшируется в Redis (TTL 60 сек).
Решение
По языку: сохранить Go
Обоснование:
- Прототип уже работает и проверен в продакшене — переписывать означает потерять как минимум 2-4 недели
- AI-генерация — экспериментальная гипотеза. Если фича не приживётся, вложения в переписывание потеряны
- Go хорошо подходит для IO-bound нагрузки с конкурентными воркерами (goroutines) — типичный паттерн для обработки очереди AI-заданий
- Spring Boot добавляет существенный overhead старта и памяти, который не нужен для узкоспециализированного AI-сервиса
- Отступление от стандарта зафиксировано в этом ADR — команда осведомлена
Условие пересмотра
Если сервис вырастет до core-компонента платформы (не experimental), рассмотреть перевод на Java или официальное признание Go-стека как допустимого для определённых типов сервисов.
По валидации JWT: Token Introspection (целевое) + UserInfo fetch (промежуточное)
Update 2026-05-07
Auth Service выдаёт JWT без
issиaud, и безpermissionsв payload (толькоsub,franchise_id,role_ids). Поэтому реализован двухэтапный подход:
- Локальная валидация подписи + срока действия (HS256, общий
JWT_SECRETс auth-service).iss/audне enforce-ятся (envJWT_ISSUER/JWT_AUDIENCEпусты).- Обогащение Claims через
GET /api/v1/auth/me(Bearer-токен пользователя, кэш 60 сек). Это даётpermissions[],franchise_id,scope. Service-token не требуется на этом этапе.Целевая модель —
POST /internal/auth/validateсX-Service-Token(как у Admin BFF / POS BFF) — будет включена когда ERP-команда выпустит service-token дляgensvc. Переключение через одну env-переменную (AUTH_USERINFO_URL→AUTH_INTROSPECTION_URL+AUTH_SERVICE_TOKEN).
Выбран Вариант 3 — POST /internal/auth/validate (целевая модель, после получения service-token).
Обоснование:
- Endpoint уже реализован в Auth Service (контракт). Изменений в Auth Service не требуется
- Возвращает актуальные
permissions[]— именно они нужны для проверкиgensvc.photo.create,gensvc.preset.adminи т.д. JWKS-подход permissions не даёт - Аналогично тому, что делают Admin BFF и POS BFF — единообразие в экосистеме
- Redis-кэш в самом Photo Studio Service (TTL 60 сек по хэшу токена) — latency сопоставима с локальным фильтром
- Centralized revocation: если сотруднику убрали права, через ≤ 60 сек новые вызовы это отразят
JWKS отклонён — не передаёт permissions, нужна дополнительная логика. Token Exchange отклонён — требует новый endpoint в Auth Service, усложняет Auth Service.
Схема взаимодействия
sequenceDiagram participant Client as Фронтенд participant PS as Photo Studio Service participant Redis participant Auth as Auth Service Client->>PS: POST /api/v1/gensvc/jobs/photo<br/>Authorization: Bearer <JWT> PS->>Redis: GET introspect:{token_hash} alt Cache hit (TTL < 60s) Redis-->>PS: { valid, user_id, franchise_id, permissions } else Cache miss PS->>Auth: POST /internal/auth/validate<br/>X-Service-Token: {AUTH_SERVICE_TOKEN}<br/>{ "token": "..." } Auth-->>PS: { valid: true, user_id, franchise_id, permissions: ["gensvc.photo.create", ...] } PS->>Redis: SET introspect:{token_hash} ... EX 60 end PS->>PS: Проверить permissions["gensvc.photo.create"] PS-->>Client: 202 { job_id, status_url, stream_url }
Последствия
Положительные
- Прототип интегрируется без переписывания кода
- Permissions-модель единая со всей платформой —
gensvc.*ключи управляются через Админку Франшизы - Auth Service не модифицируется — нет риска регрессии
- Revocation работает через кэш (≤ 60 сек)
Отрицательные
- Go — нестандартный стек для ERP. Два языка в команде
- Нет генерации клиентского кода из OpenAPI (Spring Boot умеет)
- При ротации
AUTH_SERVICE_TOKENнужно обновить env Photo Studio Service
Риски
- Если Auth Service временно недоступен — Photo Studio Service не может валидировать новые токены. Митигация: при Redis cache-hit сервис работает нормально; при miss и ошибке вызова Auth —
503с описанием - Shared service token (
AUTH_SERVICE_TOKEN) — секрет, который нельзя коммитить
Условия пересмотра
- Появился механизм token revocation в реальном времени (< 1 сек) — тогда JWKS + blacklist может стать предпочтительнее
- Photo Studio Service масштабируется до core-сервиса — рассмотреть Java
- Auth Service получит JWKS с permissions в стандартных claims — тогда introspection можно заменить на локальную валидацию