В этой статье рассмотрим реализацию небольшого функционала - добавление отзывов пользователями с возможностью прикрепить фотографии, загрузить их в облако и получить ссылку файл из S3, а именно Yandex Object Storage, используя AWS SDK Java. Привет! Меня зовут Никита, я начинающий Java разработчик. В одном из своих учебных проектов создал веб-приложение, которое позволяет пользователям делиться информацией об интересных событиях и находить компанию для участия в них. Одна из фич приложения - добавление отзывов о посещенных событиях. Как и у многих подобных сервисов, возможность добавлять фотографии к своим отзывам - мастхев. Пытаясь правильно реализовать эту функциональность, не смог найти ни одной статьи, в которой можно углубиться в тему комплексно. Поэтому, собрав всю информацию, которая может пригодиться, спешу поделиться своим решением! Java 11, Maven, Spring Boot, Hibernate, Lombok, Docker, RestTemplate, AWS SDK Проект разделен на два сервиса:  main - содержит функционал по созданию, удалению и получению отзывов и пользователей. В рамках данного тестового проекта в основном сервисе имеем две основные сущности: Comment и User upload - содержит клиент для обработки внутренних запросов из сервиса main и сам функционал добавления фотографий на S3 Для работы с AWS SDK необходимо иметь следующие зависимости в upload-сервисе: docker-compose.yml: Так как цель загрузки фотографий на облако - использование их при отображении отзыва в веб-интерфейсе, принято решение хранить ссылки на фотографии в базе данных PostgreSQL с привязкой к отзыву для удобной выгрузки без постоянного обращения к S3. Рассмотрим контроллеры основного сервиса. На создании пользователя останавливаться не буду, важно только учесть, что перед отправкой отзыва хоть какой-то человек должен быть создан (можно в принципе и кота зарегать). Взглянем на DTO при создании комментария отзыва, он нам еще пригодится. Так как не все пользователи охотно прикрепляют фотографии к своим отзывам, стоит учесть, что его можно создать и без фотографий. В таком случае он создается просто с пустым списком ссылок на фото в ответе. А вот, кстати, и ответ сервера:  Пора реализовать добавление отзыва в сервисе. Создаем интерфейс CommentService, описываем его контракт, затем создаем его имплементацию и получаем: О чем я говорил ранее, пользователи могут писать отзывы и не прикреплять фото, поэтому просто проверяем на наличие загруженных фотографий и если их нет - сохраняем пустой список.  Запрос на добавление отзыва (вспоминаем про DTO при создании) отправляется с заголовком Content-Type: multipart/form-data. Параметр photos соответственно может отсутствовать. При наличии фотографий мы отправляем их через созданный RestTemplate upload-client, который расщепляет фотографии в байты, и затем отправляет их на upload-server. Последний загружает в Yandex Object Storage, используя многопоточность Java. Двигаемся дальше. Получив фотографии с основного сервиса, клиент делает свои небольшие изменения, о которых я говорил выше, и отправляет на сервер запрос на загрузку. Теперь рассмотрим сам метод загрузки детальнее. Код, представленный ниже, создает подключение к S3, в нашем случае к Yandex Object Storage, используя accessKeyId и secretAccessKey - статические ключи доступа. Инструкция по созданию. Помимо этого в будущем понадобится имя бакета bucketName. В своем приложении я указываю их в docker-compose.yml и инжекчу с помощью аннотации @Value Чтобы добавить фотографию в бакет, нужно вызвать метод putObject() Аналогичным способом, но через создание объекта PutObjectRequest Также можно настроить доступ к чтению файла, если у бакета не включен публичный доступ  Объект метаданных создается следующим образом. Можно установить заголовок, длину контента и т.д., в зависимости от ваших потребностей. В моей реализации мне необходимо возвращать список ссылок на загруженные фотографии, чтобы привязать их к отзыву, поэтому давайте достанем ссылку из S3. Это можно сделать методом getUrl() При поочередной загрузке циклом 10 фотографий в отзыв, у меня это заняло 6 секунд. Здесь явно напрашивается выполнение каждой загрузки в отдельном потоке, при этом мы понимаем, что будет в дальнейшем ограничение на количество возможных фотографий к прикреплению. Значит потоками можно легко управлять.  Обращаемся к Java Concurrency. Создаем пул потоков ExecutorService, который будет управлять загрузкой фотографий, а также список Future, который представляет собой обещание будущего результата. Каждая задача загрузки фотографии будет возвращать URL загруженного файла в виде строки, и эти обещания будут использоваться для получения результатов после завершения задач. Используем executorService.submit() для отправки задач на выполнение в пул потоков. Этот метод принимает Callable<String> (или Runnable) и возвращает Future, представляющий результат выполнения задачи. Внутри лямбда-выражения выполняется код загрузки фотографии и получения URL. Результат (URL) возвращается из задачи. После отправки всех задач на выполнение, мы проходим по списку futures и ожидаем завершения каждой задачи с помощью метода future.get(). Этот метод блокирует выполнение до тех пор, пока задача не завершится, и возвращает результат. И, конечно же, в конце вызываем executorService.shutdown() для корректного завершения пула потоков.  В итоге производительность увеличилась в 5 раз. Аналогичное создание отзыва с такими же десятью фотографиями заняло 1 с лишним секунду. Таким образом, реализация метода upload() получилась следующим образом: Использование AWS SDK для Java позволяет легко интегрировать возможности Yandex Object Storage или Amazon S3 в ваши приложения, обеспечивая надежное и масштабируемое хранение и доступ к файлам. Правильная настройка и использование этого SDK может значительно упростить задачи работы с объектными хранилищами, позволяя сосредоточиться на разработке вашего приложения. Надеюсь, у меня получилось создать небольшой гайд и раскрыть данную тему. Весь код разместил у себя в GitHub. Вы можете посмотреть его подробнее и запустить это решение локально.  Спасибо всем, кто дочитал до конца! Буду рад вашим комментариям, советам и критике, а также best-practice в реализации данного функционала в коммерческой разработке!   Java разработчик