Заставляем работать MinIO и Postgresql вместе

от автора

Всем привет, в этой статье я хочу рассказать, как у меня получилось интегрировать MinIO и Postgres, а именно, что после каждой загрузки картинки в объектное хранилище у нас появляется запись в БД.
В рамках данной статьи я не буду рассматривать, как работать с MinIO, про это есть хорошая статья на Хабре «Зачем и как хранить объекты на примере MinIO»

Как было раньше

Примеры кода буду показывать максимально упрощенным, чтобы не возникало трудностей.
На проекте есть функционал загрузки изображений для ресторанов/блюд/категорий блюд через REST API. Мы загружаем картинки самим примитивным способом.

func createRestaurant(ctx *gin.Context) {     // чтение запроса и валидация его для сохранения ресторана в приложении      // ...        // Забираем изображения ресторана из тела запроса     form, err := ctx.MultipartForm() files := form.File["image"] if len(files) == 0 { ctx.JSON(http.StatusBadRequest, gin.H{ "error": "no photos", }) return }      // .... }

После всех приготовлений мы шли в БД и в рамках транзакции сохряняли запись о ресторане в первой таблице, а потом делали запись или записиь об изображениях связанные с нашим рестораном. Связь у нас один-ко-многим, у одного ресторана может быть несколько изображений в профиле.

Схема базы данных

Схема базы данных

Вроде бы все хорошо, схема рабочая как швейцарские часы, но мы программисты всегда хотим что-то да переделать и именно поэтому, когда я нашел про такой функционал MinIO как интеграция, пошел сразу же реализовывать на своем проекте.

Как теперь

Сейчас после загрузки изображений в S3 хранилище больше не нужно трогать БД собственноручно, за нас все сделают
Чтобы достигнуть этого, надо настроить наш сервер MinIO, гайд по развертыванию и работе с MinIO сервером есть все в той же статье, что я указал выше. Для тестового окружения приложу docker-compose.yaml, где у нас есть Postgresql и MinIO

version: '3.8' services:   minio:     container_name: minio     image: minio/minio     environment:       MINIO_ACCESS_KEY: minio       MINIO_SECRET_KEY: minio123     command: server /data --console-address ":9001"     ports:       - "9000:9000"       - "9001:9001"     volumes:       - minio_data:/data    postgres:     container_name: postgres     image: postgres:alpine     environment:       POSTGRES_DB: postgres       POSTGRES_USER: postgres       POSTGRES_PASSWORD: 5432     ports:       - "5432:5432"     volumes:       - postgres_data:/var/lib/postgresql/data  volumes:   minio_data:   postgres_data

Postgesql

В нашей БД создадим таблицу, которая будет хранить запись обо всех изображениях

CREATE TABLE IF NOT EXISTS images (     key TEXT,     value JSONB );

Minio

Для отправки каких либо событий надо нужен бакет, в котором будут храниться изображения для этого сделаем данные команды

# создания алиаса для нашего сервера, чтобы потом его переиспользовать mc alias set myminio http://minio:9000 minio minio123 mc mb myminio/menus # создания бакета menus 

Настраиваем подключение к базе данных

mc admin config set myminio notify_postgres:minio-postgres connection_string="user=postgres password=5432 host=postgres dbname=postgres port=5432 sslmode=disable" table="images" format="namespace"

После этого нас попросят перезапустить сервис

mc admin service restart myminio

Ну и напоследок задать, чтобы MinIO делал запись в БД после каждой загрузки изображения в бакет menus

mc event add myminio/menus arn:minio:sqs::minio-postgres:postgresql --event put

Тестирование

Само собой нужно протестировать приложение, сделал тестовый запрос на создание ресторана с помощью Swagger

тестовый запрос

тестовый запрос

Теперь в БД можно наблюдать вот такой большой json с данными про объект

Данные объекта

Данные объекта

Теперь для нашего приложения, чтобы отслеживать принадлежность изображеня для определленого ресторана или блюда будем класть метадату при загрузке изображения

func makeMetadata(opts *SaveObjectOptions) map[string]string { return map[string]string{ "restaurant_id": opts.RestaurantID, "category_id":   opts.CategoryID, "dish_id":       opts.DishID, } }  func (c *Client) SaveImage(ctx context.Context, opts *SaveObjectOptions) error { info, err := c.minioClient.FPutObject(ctx, menusBucket, opts.FileName, opts.FilePath, minio.PutObjectOptions{ ContentType:  "image/png", UserMetadata: makeMetadata(opts), }) if err != nil { c.log.Error("failed to upload image", zap.Error(err)) return err }  c.log.Info("successfully uploaded of size", zap.String("filename", opts.FileName), zap.Int64("size", info.Size))  return nil }

Заключение

Надеюсь кому то поможет данная статья для реализации своих задач


ссылка на оригинал статьи https://habr.com/ru/articles/831668/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *