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