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
This commit is contained in:
@@ -12,24 +12,49 @@
|
||||
|---|---|---|
|
||||
| PostgreSQL | сейчас | Источник правды для assets, variants, hosts, statuses |
|
||||
| S3/MinIO | сейчас | Хранилище originals и generated variants |
|
||||
| API | позже | JSON API, admin operations, validation, orchestration |
|
||||
| Worker | позже | Генерация variants, upload в S3, update PostgreSQL |
|
||||
| Admin UI | позже | Управление hosts/assets/variants/presets |
|
||||
| Gateway | позже | Caddy/Souin hot cache и delivery layer |
|
||||
| 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
|
||||
-> gateway Caddy/Souin
|
||||
-> S3 ready variant
|
||||
-> generator fallback
|
||||
-> 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 отвечает на вопросы:
|
||||
@@ -49,29 +74,45 @@ S3 хранит байты:
|
||||
|
||||
Gateway отдаёт картинки:
|
||||
|
||||
- hot cache HIT - сразу из Souin;
|
||||
- cache MISS - из S3;
|
||||
- S3 MISS - через generator fallback.
|
||||
- L1 memory HIT - сразу из памяти;
|
||||
- L1 memory MISS - вызывает Backend;
|
||||
- не имеет доступа к PostgreSQL, S3 и RabbitMQ.
|
||||
|
||||
Backend не должен проксировать картинки на каждый обычный запрос. Он отдаёт JSON, статусы и URLs.
|
||||
Backend управляет процессами: PostgreSQL, S3 read, RabbitMQ enqueue, ожидание ready variant. Worker выполняет CPU-heavy generation через external imgproxy и пишет результат в S3.
|
||||
|
||||
## URL модель
|
||||
|
||||
Публичные URL должны быть стабильными и не раскрывать source URL:
|
||||
Публичные URL должны быть стабильными и не раскрывать source URL. Для Next/image provider основной URL должен принимать width/quality из loader:
|
||||
|
||||
```text
|
||||
/images/{assetId}/{variantHash}.{format}
|
||||
/images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```text
|
||||
/images/asset_123/w640_q80_cfill.avif
|
||||
/images/asset_123/w640_q80_cfill.webp
|
||||
/images/asset_123/w640_q80_cfill.jpg
|
||||
/images/asset_123/v4/card?w=640&q=80&f=auto
|
||||
/images/asset_123/v4/hero?w=1920&q=80&f=auto
|
||||
```
|
||||
|
||||
Формат лучше делать явным в URL и отдавать через `<picture>`/`srcset`, а не выбирать по `Accept` header. Так CDN/S3/Gateway cache остаётся предсказуемым.
|
||||
`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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user