215 lines
9.8 KiB
Markdown
215 lines
9.8 KiB
Markdown
# 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 являются единственным реализованным типом ассетов.
|