Backend-сервис асинхронной обработки изображений на Go.
Пользователь загружает изображение через HTTP API и выбирает необходимые операции обработки. Исходный файл сохраняется локально, metadata записываются в PostgreSQL, а задача на обработку отправляется в Apache Kafka.
Kafka consumer получает задачу и выполняет обработку изображения в фоне. После завершения результат сохраняется в файловое хранилище, а статус задачи обновляется в PostgreSQL.
- загрузка изображений через
multipart/form-data; - асинхронная обработка через Apache Kafka;
- сохранение metadata в PostgreSQL;
- генерация UUID для изображений;
- изменение размера изображения;
- создание thumbnail;
- добавление watermark;
- хранение оригиналов и обработанных файлов;
- получение статуса обработки;
- получение URL обработанного изображения;
- удаление изображения и связанных файлов;
- retry при ошибках обработки Kafka-сообщений;
- Dead Letter Queue;
- graceful shutdown HTTP-сервера;
- Docker;
- Docker Compose;
- web-интерфейс;
- автоматизация через Makefile.
HTTP API
│
▼
┌─────────┐
│ Handler │
└────┬────┘
│
▼
┌─────────┐
│ Service │
└────┬────┘
│
┌─────────┼──────────┐
│ │ │
▼ ▼ ▼
PostgreSQL FileStore Kafka Producer
│
▼
┌─────────────┐
│ image-jobs │
└──────┬──────┘
│
▼
Kafka Consumer
│
▼
Image Processor
│
┌─────────┴─────────┐
▼ ▼
FileStore PostgreSQL
После загрузки изображение получает статус:
pending
Когда Kafka consumer начинает обработку:
processing
После успешного завершения:
done
Если обработка завершается ошибкой:
failed
Таким образом:
pending → processing → done
↘ failed
HTTP-запрос не выполняет тяжёлую обработку изображения непосредственно.
После загрузки:
- создаётся UUID изображения;
- оригинал сохраняется в файловое хранилище;
- информация об изображении сохраняется в PostgreSQL;
- создаётся Kafka message;
- задача публикуется в topic
image-jobs; - клиент получает
202 Accepted.
Kafka consumer независимо получает сообщение и выполняет фактическую обработку.
Такой подход отделяет HTTP API от ресурсоёмкой обработки изображений.
Основной topic:
image-jobs
Consumer group:
image-processor-group
Для неуспешно обработанных сообщений используется:
image-jobs-dlq
Kafka processor настроен на:
max attempts: 3
base retry delay: 200 ms
max retry delay: 2 s
Если сообщение не удаётся успешно обработать после повторных попыток, оно может быть отправлено в Dead Letter Queue.
В текущей реализации доступны три операции.
resize=true
Изображение уменьшается до ширины 500 пикселей с сохранением пропорций.
Используется фильтр:
Lanczos3
thumb=true
Создаётся thumbnail:
200 × 200
watermark=true
На изображение добавляется простая светлая плашка в нижнем правом углу.
Операции можно комбинировать.
Например:
resize=true
thumb=true
watermark=true
Для декодирования используются стандартные Go image packages.
Поддерживается чтение:
- JPEG;
- PNG.
Обработанный результат сохраняется в:
JPEG
с качеством:
90
Файлы разделены на три категории:
uploads/
├── original/
├── processed/
└── thumbs/
Исходные изображения.
Результаты основной обработки.
Миниатюры изображений.
При удалении изображения сервис удаляет:
- оригинал;
- обработанный файл;
- thumbnail;
- запись из PostgreSQL.
PostgreSQL используется для хранения состояния задачи и metadata изображения.
Для каждого изображения сохраняются:
- UUID;
- исходное имя;
- MIME type;
- статус обработки;
- выбранные операции;
- пути к исходному и обработанным файлам;
- текст ошибки;
- timestamps.
POST /upload
Content-Type: multipart/form-dataПараметры:
| Поле | Описание |
|---|---|
file |
изображение |
resize |
выполнить resize |
thumb |
создать thumbnail |
watermark |
добавить watermark |
Пример:
curl -X POST http://localhost:8080/upload \
-F "file=@photo.jpg" \
-F "resize=true" \
-F "thumb=true" \
-F "watermark=true"При успешной постановке задачи сервис отвечает:
202 AcceptedПример ответа:
{
"id": "94da43a4-5a67-4f05-b26e-a67fe32cb61c",
"status": "pending"
}GET /image/<id>Например:
curl http://localhost:8080/image/94da43a4-5a67-4f05-b26e-a67fe32cb61cВо время обработки:
{
"id": "94da43a4-5a67-4f05-b26e-a67fe32cb61c",
"status": "processing"
}После завершения:
{
"id": "94da43a4-5a67-4f05-b26e-a67fe32cb61c",
"status": "done",
"image_url": "/files/processed/94da43a4-5a67-4f05-b26e-a67fe32cb61c.jpg",
"thumb_url": "/files/thumbs/94da43a4-5a67-4f05-b26e-a67fe32cb61c.jpg"
}Обработанные изображения доступны через:
/files/processed/
Thumbnail:
/files/thumbs/
Например:
http://localhost:8080/files/processed/<image-id>.jpg
DELETE /image/<id>Пример:
curl -X DELETE \
http://localhost:8080/image/94da43a4-5a67-4f05-b26e-a67fe32cb61cОтвет:
{
"result": "deleted"
}Статические файлы доступны по:
http://localhost:8080/
.
├── cmd
│ └── image-processor
│ └── main.go
│
├── configs
│ └── config.yaml
│
├── internal
│ ├── broker
│ │ ├── consumer.go
│ │ └── producer.go
│ ├── config
│ ├── dto
│ ├── handler
│ ├── model
│ ├── repository
│ ├── service
│ │ ├── processor.go
│ │ └── service.go
│ ├── storage
│ └── transport
│
├── migrations
├── static
├── Dockerfile
├── docker-compose.yml
├── makefile
├── go.mod
└── go.sum
git clone https://github.com/MarmulevSemyon/image-processing-service.git
cd image-processing-serviceРекомендуемый способ:
make docker-upПеред запуском Docker Compose выполняется цепочка:
fmt → vet → lint → build
После этого запускаются:
- PostgreSQL;
- ZooKeeper;
- Apache Kafka;
- Kafka topic initializer;
- Image Processing Service.
Kafka initializer автоматически создаёт:
image-jobs
image-jobs-dlq
Сервис доступен:
http://localhost:8080
PostgreSQL с host-машины:
localhost:5433
Kafka с host-машины:
localhost:9093
make docker-logsmake docker-downmake docker-restartКоманда выполняет:
docker compose down -v
docker compose up --build -dПри выполнении
docker-restartудаляется PostgreSQL volume, поэтому данные базы будут потеряны.
make runКоманда:
go run ./cmd/image-processorДля локального запуска необходимо самостоятельно обеспечить доступ к PostgreSQL и Kafka либо изменить конфигурацию приложения.
make buildПеред сборкой автоматически выполняется:
fmt → vet → lint → build
Бинарный файл:
bin/image-processor
make fmtВыполняется:
gofmt -w ./cmd ./internalmake vetЦель vet зависит от fmt.
make lintЦепочка:
fmt
↓
vet
↓
golint
Если golint не установлен:
go install golang.org/x/lint/golint@latest| Команда | Назначение |
|---|---|
make run |
локальный запуск приложения |
make build |
проверки и сборка |
make fmt |
форматирование Go-кода |
make vet |
форматирование + go vet |
make lint |
форматирование + go vet + golint |
make docker-up |
сборка + запуск Docker Compose |
make docker-down |
остановка Docker Compose |
make docker-logs |
просмотр логов |
make docker-restart |
полный перезапуск с удалением volumes |
make clean |
удаление бинарных файлов |
make cleanУдаляет:
bin/
- Go
- Apache Kafka
- PostgreSQL
- HTTP / REST API
- JSON
- Multipart Form Data
- Image Processing
- Kafka Consumer Groups
- Retry
- Dead Letter Queue
- Docker
- Docker Compose
- Makefile
- WB Framework
Организация backend-приложения с разделением ответственности между:
transport
handler
service
repository
broker
storage
Отделение HTTP API от непосредственной обработки изображения.
HTTP-handler принимает запрос и публикует задачу, а фактическая обработка выполняется Kafka consumer'ом независимо от HTTP-request lifecycle.
Работа с:
- producer;
- consumer;
- topics;
- consumer groups;
- message keys;
- JSON payload;
- retry;
- Dead Letter Queue.
Обработка временных и постоянных ошибок Kafka consumer'а с ограниченным количеством повторных попыток и возможностью отправки проблемных сообщений в отдельный DLQ topic.
Хранение:
- metadata;
- состояния задачи;
- параметров обработки;
- путей к файлам;
- ошибок обработки.
Практическая работа со стандартным Go Image API и библиотекой resize:
- decoding;
- resize;
- thumbnail;
- drawing;
- JPEG encoding.
Организация локального файлового хранилища с раздельным хранением:
- оригиналов;
- обработанных изображений;
- thumbnails.
При ошибке отправки задачи в Kafka сервис удаляет уже сохранённый исходный файл и запись PostgreSQL, предотвращая появление незавершённого объекта.
Реализация:
- multipart upload;
- JSON responses;
- HTTP status codes;
- GET;
- POST;
- DELETE;
- static file serving.
Использование UUID как уникальных идентификаторов изображений и Kafka message key.
Обработка:
SIGINT
SIGTERM
и корректная остановка HTTP-сервера.
Оркестрация нескольких компонентов:
Go application
PostgreSQL
ZooKeeper
Kafka
Kafka initializer
Автоматизация:
- форматирования;
- статического анализа;
- линтинга;
- сборки;
- локального запуска;
- управления Docker Compose.
Основная цель проекта — практическая работа с асинхронной обработкой задач, Apache Kafka, PostgreSQL, файловым хранилищем, image processing и архитектурой backend-сервисов.