Всем привет, в этой статье я хочу рассказать, как у меня получилось интегрировать 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/
Добавить комментарий