Files
image-platform/docs/architecture.md
S.Gromov bcadb85a83 feat: добавить базовые сервисы image-platform
- добавлены backend, admin, gateway и worker skeleton
- добавлены Drizzle schema, database package и initial migration
- добавлены shared packages для RabbitMQ topology и S3 helpers
- обновлены dev-инфраструктура, env example, scripts и dependencies
- обновлена документация под versioned image URLs и read-through flow
2026-05-05 09:59:21 +03:00

5.9 KiB
Raw Blame History

Архитектура

Назначение

image-platform - отдельный control plane для своего Cloudinary-like image pipeline.

Проект отвечает за metadata, S3 artifacts, variants, allowlist, presets и генерацию изображений через внешний imgproxy.

Компоненты

Компонент Статус Роль
PostgreSQL сейчас Источник правды для assets, variants, hosts, statuses
S3/MinIO сейчас Хранилище originals и generated variants
Backend сейчас NestJS JSON API, Swagger, PostgreSQL/S3/RabbitMQ orchestration
Worker позже RabbitMQ consumer, imgproxy processing, upload в S3, update PostgreSQL
Admin UI сейчас React/Vite UI для будущего управления hosts/assets/variants/presets
Gateway сейчас Fastify public image origin, L1 memory cache, root routing, без DB/S3 доступа
RabbitMQ сейчас Очередь задач генерации variants
imgproxy external CPU-heavy image processing

Архитектурное решение

Нужное поведение - Cloudinary-like: публичный URL изображения сам запускает read-through pipeline, если variant ещё не создан.

image-platform строится ради совместимости с next/image как custom loader provider. Next.js application не должен заранее вызывать API, ждать генерацию и затем подставлять S3 URL. Он должен передавать src, width и quality в loader, а loader должен вернуть стабильный URL нашего image origin.

Gateway поэтому является обязательной частью public delivery path, а не опциональным кешем.

Целевая delivery схема

client
-> CDN optional
-> Fastify gateway L1 memory cache
-> NestJS backend
-> PostgreSQL + S3 ready variant
-> RabbitMQ -> worker
-> external imgproxy
-> source image

Read-through flow:

1. client запрашивает /images/{assetId}/v{version}/{preset}?w=640&q=80&f=auto
2. CDN HIT -> ответ сразу
3. Gateway L1 HIT -> ответ сразу
4. Gateway L1 MISS -> Gateway вызывает Backend internal ensure endpoint
5. Backend проверяет PostgreSQL/S3
6. S3 HIT -> Backend стримит bytes Gateway, Gateway кладёт ответ в L1
7. S3 MISS -> Backend ставит RabbitMQ job
8. Worker вызывает external imgproxy и сохраняет результат в S3
9. Worker обновляет PostgreSQL, Backend отдаёт готовые bytes Gateway
10. Gateway кладёт ответ в L1 и отдаёт клиенту

Разделение ответственности

PostgreSQL отвечает на вопросы:

  • какие assets зарегистрированы;
  • какие variants созданы;
  • где variants лежат в S3;
  • какие variants pending, processing, ready, failed;
  • сколько bytes занимает asset/project/user;
  • какие source hosts разрешены.

S3 хранит байты:

  • original images, если решим сохранять originals;
  • generated variants;
  • metadata объектов на уровне storage, но не бизнес-логику.

Gateway отдаёт картинки:

  • L1 memory HIT - сразу из памяти;
  • L1 memory MISS - вызывает Backend;
  • не имеет доступа к PostgreSQL, S3 и RabbitMQ.

Backend управляет процессами: PostgreSQL, S3 read, RabbitMQ enqueue, ожидание ready variant. Worker выполняет CPU-heavy generation через external imgproxy и пишет результат в S3.

URL модель

Публичные URL должны быть стабильными и не раскрывать source URL. Для Next/image provider основной URL должен принимать width/quality из loader:

/images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto

Примеры:

/images/asset_123/v4/card?w=640&q=80&f=auto
/images/asset_123/v4/hero?w=1920&q=80&f=auto

v{version} берётся из image_assets.current_version и меняется при обновлении source image. Это даёт immutable cache без purge старых CDN/L1/S3 keys.

f=auto нужен для совместимости с next/image custom loader: Next передаёт в loader src, width и quality, но не выбирает AVIF/WebP сам при custom loader. Image origin должен выбрать формат по Accept header, как Cloudinary f_auto.

Из-за f=auto обязательно:

  • S3 key должен включать фактически выбранный формат;
  • response должен выставлять Vary: Accept;
  • CDN и Gateway L1 cache должны учитывать Accept;
  • response должен выставлять Cache-Control: public, max-age=31536000, immutable для versioned assets.

Для ручного <picture>/srcset можно добавить явный формат позже:

/images/{assetId}/v{version}/{preset}?w=640&q=80&f=avif
/images/{assetId}/v{version}/{preset}?w=640&q=80&f=webp
/images/{assetId}/v{version}/{preset}?w=640&q=80&f=jpg

External imgproxy

imgproxy не входит в этот проект и не деплоится вместе с ним. Он подключается через env:

IMGPROXY_UPSTREAM=http://external-imgproxy.internal:8080

Это позволяет держать image processing на отдельной мощной машине и не рисковать основным сервером.