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.

Рассмотренные варианты

  1. Сохранить Go — интегрировать прототип как есть, принять отступление от стандарта
  2. Переписать на Java — создать новый erp-photo-studio-service на Spring Boot по образцу остальных сервисов
  3. Обёртка-адаптер — Java-сервис как тонкий gateway, Go-движок без REST API

Проблема 2: Валидация JWT

При интеграции Photo Studio Service с Auth Service есть три подхода:

  1. JWKS-only — сервис сам скачивает публичные ключи с JWKS endpoint Auth Service и валидирует подпись локально. Не требует сетевого вызова на каждый запрос, но не получает актуальный список permissions (они не входят в JWT payload).
  2. Token Exchange — новая схема: сервис обменивает пользовательский токен на токен со встроенными permissions. Требует нового endpoint в Auth Service, нет в текущем контракте.
  3. 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). Поэтому реализован двухэтапный подход:

  1. Локальная валидация подписи + срока действия (HS256, общий JWT_SECRET с auth-service). iss/aud не enforce-ятся (env JWT_ISSUER/JWT_AUDIENCE пусты).
  2. Обогащение 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_URLAUTH_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 можно заменить на локальную валидацию

Ссылки