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

126 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура
## Назначение
`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 схема
```text
client
-> CDN optional
-> Fastify gateway L1 memory cache
-> NestJS backend
-> PostgreSQL + S3 ready variant
-> RabbitMQ -> worker
-> external imgproxy
-> source image
```
Read-through flow:
```text
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:
```text
/images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto
```
Примеры:
```text
/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` можно добавить явный формат позже:
```text
/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:
```env
IMGPROXY_UPSTREAM=http://external-imgproxy.internal:8080
```
Это позволяет держать image processing на отдельной мощной машине и не рисковать основным сервером.