This commit is contained in:
2026-05-12 07:54:32 +03:00
parent 0faa8b9d2d
commit d49449c30c
187 changed files with 4826 additions and 5884 deletions

View File

@@ -0,0 +1,214 @@
# Assets Delivery Platform
## Концепция
`image-platform` эволюционирует из платформы для изображений в Assets Delivery Platform.
Платформа должна быть control plane и delivery layer для загрузки, обработки, версионирования и доставки ассетов. Первый поддерживаемый тип ассетов - изображения. Текущие наработки по image pipeline остаются основой первого production-ready vertical slice.
## Продуктовая цель
Пользователь должен иметь возможность управлять ассетами через кабинет и программно через API.
Клиентский backend должен уметь без участия UI:
- загрузить изображение;
- выбрать preset обработки;
- запустить build;
- получить статус обработки;
- получить готовые delivery URL;
- использовать публичные URL в приложении, CMS, магазине или любом другом сервисе.
Платформа должна быть не только оптимизатором изображений, а headless-сервисом доставки ассетов с UI для управления.
## Первый vertical slice: Images
Первый модуль платформы - изображения.
В рамках images сохраняются текущие архитектурные решения:
- `Gateway` как публичный image origin;
- read-through delivery flow;
- `Backend` как orchestration/control plane;
- `PostgreSQL` как источник правды для metadata, statuses и variants;
- `S3/MinIO` как хранилище originals и generated variants;
- `RabbitMQ` как очередь задач;
- `Worker` как исполнитель image processing;
- внешний `imgproxy` как CPU-heavy image processor;
- versioned immutable public URLs;
- presets и variants;
- `f=auto` с negotiation по `Accept` header.
Текущий публичный URL для managed images остаётся базовым delivery contract:
```text
GET /images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto
```
## Кабинет пользователя
На первом этапе кабинет показывает только раздел работы с изображениями.
Пользователь должен иметь возможность:
- загружать изображения;
- создавать и редактировать image presets;
- смотреть список загруженных изображений;
- смотреть версии, variants, статусы обработки и готовые URL;
- выпускать API-ключи для проекта.
В будущем кабинет расширяется разделами:
- Images;
- Videos;
- Sprites;
- Fonts;
- другие типы ассетов при появлении продуктовой необходимости.
## Projects
`Project` становится основной областью изоляции.
К проекту должны относиться:
- assets;
- presets;
- builds;
- variants/results;
- API keys;
- лимиты;
- настройки delivery;
- allowlist источников;
- usage/billing metrics, если они появятся.
На первом этапе можно развивать images внутри проекта, не создавая преждевременно универсальную модель для всех типов ассетов.
## API Keys
Для каждого проекта пользователь может выпускать API-ключи.
API-ключ нужен для server-side интеграций, где backend клиента программно добавляет файлы, запускает обработку и получает результаты.
Ключ должен храниться безопасно:
- secret показывается пользователю только при создании;
- в базе хранится hash секрета;
- в UI отображается только prefix/identifier;
- ключ можно отозвать;
- ключ может иметь scopes;
- желательно хранить дату последнего использования.
Базовые scopes:
```text
assets:read
assets:write
assets:delete
presets:read
presets:write
builds:read
builds:write
```
Delivery URLs остаются публичными и не требуют `Authorization`, если конкретный проект не включает приватный delivery mode.
## Headless API
Платформа должна предоставлять публичный management API для backend-клиентов.
Минимальный image API первого этапа:
```text
POST /api/v1/images
GET /api/v1/images
GET /api/v1/images/{id}
DELETE /api/v1/images/{id}
POST /api/v1/images/{id}/builds
GET /api/v1/images/{id}/builds
GET /api/v1/images/{id}/results
GET /api/v1/image-presets
POST /api/v1/project-api-keys
GET /api/v1/project-api-keys
DELETE /api/v1/project-api-keys/{id}
```
API должен поддерживать загрузку файла напрямую, а не только регистрацию внешнего `sourceUrl`.
Пример server-side сценария:
```text
1. Backend клиента отправляет изображение в API платформы с project API key.
2. Платформа сохраняет original, создаёт asset и version.
3. Backend клиента указывает preset или запускает build.
4. Worker генерирует variants/results.
5. Backend клиента получает готовые public delivery URL.
```
## Builds и Results
`Build` описывает запуск обработки ассета по preset или transform config.
`Result` или `Variant` описывает готовый артефакт, который можно доставлять через public URL.
Для images текущая сущность `image_variants` уже выполняет роль результата обработки. При развитии API можно добавить explicit build layer, не ломая текущий read-through delivery flow.
## Realtime transforms и cropping
Платформа должна поддерживать два режима обработки изображений:
- preset builds - заранее заданные и ограниченные variants;
- realtime transforms - динамические resize/crop/format/quality операции через delivery URL.
Realtime crop должен быть ограничен правилами проекта и preset/custom transform config, чтобы пользователь не мог бесконтрольно создавать произвольные дорогие трансформации.
Первый запрос на dynamic transform может генерировать результат через worker/imgproxy и сохранять его в storage/cache. Следующие запросы должны отдавать уже готовый артефакт.
Пример будущего dynamic transform URL:
```text
GET /images/{assetId}/v{version}/custom?w=800&h=600&fit=fill&crop=center&f=auto&q=80
```
Параметры, влияющие на bytes, должны входить в deterministic variant hash и S3 key.
## Workers
Для каждого типа ассетов предусматривается специализированный worker.
Общий orchestration остаётся в backend, database, queue и storage. Worker конкретного типа отвечает за инструменты обработки этого типа.
Планируемая модель:
```text
image-worker -> imgproxy / sharp / imagemagick
video-worker -> ffmpeg
font-worker -> fonttools / subset tools
sprite-worker -> svg/css sprite builder
```
На первом этапе реализуется и развивается `image-worker`. Остальные worker'ы добавляются только при появлении соответствующих продуктовых задач.
## Архитектурный принцип
Не нужно преждевременно строить универсальный engine для всех возможных ассетов.
Правильное направление:
- делать images как первый полноценный модуль;
- общие сущности называть так, чтобы они не блокировали будущие типы ассетов;
- выносить в общий слой только реально общие части: projects, API keys, queue orchestration, storage contract, statuses, delivery concepts;
- типоспецифичную обработку держать внутри конкретного модуля.
Примеры naming direction:
```text
Project вместо ImageProject
ProjectApiKey вместо ImageApiKey
ProcessingJob вместо ImageWorkerJob
AssetBuild вместо ImageBuild, если build станет общим понятием
```
При этом текущие `image_assets`, `image_asset_versions` и `image_variants` могут оставаться конкретными image-таблицами, пока images являются единственным реализованным типом ассетов.