Спасибо вам большое за такие уроки. После получения в университете и на просторах интернета знаний очень помогает разложить все по полочкам, а также узнать что-то новое. Начинаю первый полностью свой проект по вашему курсу!
Спасибо за видео! ) Хотела заметить, что для формирования ResponseEntity удобнее (как мне кажется) пользоваться готовым функционалом Spring Hateoas! ;)
Привет. Отличный курс, большая работа проделана, виден системный подход. Большой лайк за труды. Вопрос по блоку возвращения ResponseEntity при обработке ошибок. Заметил, что статус ошибки заполняется два раза, сначала в ProblemDetail, потом как статус ResponseEntity, а problemDetail возвращается в body. В этом есть сакральный смысл (спрашиваю, т.к. есть ощущение, что просто так ничего в роликах не говорится и не делается)? Я у себя сделал вариант ResponseEntity .of(problemDetails) .build() Вроде работает ))
Классные видео, спору нет, но не разделяю шквал похвалы, тк задача автора не только всё детально показать, но и доходчиво объяснить. На мой взгляд данное видео местами сложновато для понимания новичками, и вызывает не мало вопросов, это на фоне других обучающих материалов по спрингу, где объясняют на примерах необходимого минимума.
Спасибо автору за отличный материал. У меня вопрос: объясните пожалуйста почему сервис и менеджер крутятся на разных портах, если мы в ямл файлах прописали один порт? Почему в постман мы посылаем запросы на порт 8081, а в браузере видим порт 8080? Вот этот момент до сих пор понять не могу.
В сервисе каталога порт - 8081 github.com/alex-kosarev/sc24/blob/SC24EP02-servlet-api-rest-service/catalogue-service/src/main/resources/application-standalone.yaml
8:00 - Когда речь идет про три разных варианта класса данных Product (для представления, базы данных и самого объекта), то нужны некие преобразователи из одного варианта в другой? Их реализовывать в контроллере и репозитории соответственно? Это получается что-то вроде адаптеров в гексагональной архитектуре?
По идее да, должны быть преобразователи, можно преобразования писать прямо в контроллере и репозитори, можно выносить в отдельные классы. Да, контроллер и репозиторий являются адаптерами в контексте гексагональной архитектуры
Александр, спасибо за видео, с интересом посмотрел. Печалька, что такой полезный класс как ProblemDetail сделали только в 6 версии Spring, не могу его использовать - у нас на проекте 5-я версия)
Метод getProduct (21 строка) вызывается перед findProduct (27 строка), его задача - добавить в модель товар, чтобы потом из модели передать товар в аргументы метода findProduct
@@shurik_codes Добрый день. Через хэндлер отлавливается, просто нужно в настройках ProblemDetail подключить. К сожалению для ошибок валидации все равно придется в хендлере из BindingResult вытаскивать. Жаль стандарт не включает ошибки валидации и при тесте приходится заморачиваться с проверкой проперти, хотя можно просто расширить ProblemDetail, что мне кажется предпочтительней.
@@shurik_codes Если у нас провалится валидация из Payload, вылетит MethodArgumentNotValidException - его как раз и будем перехватывать. Думаю, не стали заморачиваться с хэндлерами просто для экономии времени, а так конечно, ошибки (и тем более их обработку) обычно не ловят в контроллерах
Я верно понимаю, что ResponseEntity по сути нужен только, если требуется кастомизация статуса? Если достаточно каких-то дефолтных (на успех это 200, а на ошибки не помню, что там спринг по умолчанию использует), то соответственно из методов контроллеров возвращаем просто нужный нам объект?
Спасибо за видео, уже становится сложнее всё запоминать из-за большой плотности информации, хоть и не впервой это вижу. Хоть уроков уже много выпущено, хотелось бы попросить озвучивать горячие клавиши, используемые при разработке, чтобы научиться так же быстро управляться с кодом, и прочие фишки IDEA . Ещё хотел уточнить, DTO не создаются для упрощения уроков и отсутствия работы с БД(на данный момент) или их создание устаревшая практика?
@@artyomzolotoverkhov8468 Это-то я несомненно делаю, просто смотреть вообще бестолку) Но по ему опыту, информация более менее железно укладывается только через пол года повторений
Здраствуйте! 29:45 Не пойму смысл if (bindingResult.hasErrors()) { if (bindingResult instanceof BindException exception) { throw exception; } else { throw new BindException(bindingResult); } } Почему нельзя сократить до if (bindingResult.hasErrors()) { throw new BindException(bindingResult); }
Заметил у вас интересную штуку. Вот к примеру в методе мы не находим по id и в блоке if кидаем исключение. Затем отдельный метод "ловец" этого исключения. А не проще ли не кидать эксепшн, а просто в блоке иф делать обращение к методу "ловцу"? Как-то выглядит более прямой такая логика, нет?
Я немного ненаглядно показал, да, но предполагается, что в реальных условиях исключение может возникнуть в нескольких местах, и чтобы везде не писать обработку на месте, таким образом дублируя код, гораздо удобнее написать метод с @ExceptionHandler
NoSuchElementException выбрасывается в нескольких местах, и будет странно если в одном мы будем выбрасывать в другом обращаться к методу, тем более, что оно выбрасывается в сервисе и мы оттуда не можем обращаться к контроллеру. Вообще по хорошему создать нужно какой-нибудь рест контроллер эдвайс и там уже хэндлить
@@ЭдуардВолков-к7л С помощью терминала через cd заходите в директорию вашего сервиса("cd catalogue-service", например) и в терминале пишете: mvn spring-boot:run -D"spring-boot.run.profiles"=standalone
Спасибо за видео. Но у меня есть вопрос) в Вашем примере объект UpdateProductPayload содержит всего два свойства. Предположим этих свойств гораздо больше и содержать новые данные для обновления могут не все т.е. допускается null значение. Как тогда оптимальней всего реализовать обновление объекта, может есть вариант, как избежать ручной проверки данных в каждом из свойств объекта?
@@shurik_codes Не очень понимаю, как это поможет если свойство может быть null, т.е. это значение разрешено, то при сохранении null заменит уже имеющееся значение в бд. А задача сохранить только те значения которые не null.
А, вот про что вопрос. Есть два варианта: 1. Описывать структуры данных для каждого варианта изменения, чтобы они содержали только те свойства, которые могут быть изменены 2. Для каждого варианта изменения описывать свой запрос к БД (специфично для SQL, JPA, Spring Data и т.д.)
Просто мысль вслух: можно попробовать @DynamicUpdate на сущность поставить, а при маппинге из payload в сущность null-ы пропускать (но это уже hibernate на борту подразумевается). Неизмененные поля в SQL не попадут (но потеряется перф, т.к. запрос динамический и не закешируется).
@@ИльяКалендарев-е8ж Посмотрите связку mapStruct и JsonNullable для этих целей. Если кратко, то в DTO, которые отвечают за создание и обновление сущности нужным полям нужно сделать тип JsonNullable (или не String). Это такая обёртка, которая позволит различать null явный, когда мы хотим, чтобы у сущности поле стало null и null от того, что мы не указали это поле в теле запроса. Далее при отправке запроса, в случае если это поле не будет указано в теле запроса, то его значение будет null, а если указали и явно указали ему стать null, то этот null будет зашит внутри обёртки После этого мы настраиваем mapStruct для того, чтобы такие поля корректно мапились при преобразовании DTO в объект Если интересно, могу скинуть учебный проект, где я такое реализовывал @shurik_codes также интересно ваше мнение про этот подход!
А как такие rest-сервисы масштабируются? Просто запускается еще один экземпляр, и нагрузка распределяется балансировщиком? Такая архитектура сервиса - хорошее решение для написания масштабируемых приложений?
Спасибо за ролик! С первого ролика не совсем понял, может кто нибудь объяснить почему мы используем NewPayload и UpdatePayload? Можно же вроде Product передавать?
Принцип единственной ответственности, по крайней мере одна из его трактовок. При создании и изменении объекта мы не задаём все свойства, из которых объект состоит. В добавок в разных операциях мы можем изменять разные наборы данных. На практике для каждой операции лучше иметь отдельный класс, описывающий полезную нагрузку.
@@shurik_codes Спасибо понял. А правильно ли я понимаю, что почти в таком же контексте(для не изменения сущности, и отделения ее) для таких целей при работе с базами данных используют DTO?
Про метод с ветвлениями - не понял о каком методе идёт речь. Блок else затем, чтобы выполнить код, если условие в if не сработало. Валидация выполняется не в контроллере, а в валидаторе силами фреймворка, контроллер обрабатывает результат валидации, так что никакого нарушения SRP здесь нет.
@@shurik_codes про if имел ввиду такую структуру: if(somePredicate){ return x; } return anotherX; таким образом else нет, и порядок обработки не теряется, но это, наверно, больше к код стайлу относится.
Добрый день! Спасибо за курс. У меня проблема - вроде делаю все также, но вылезает ошибка MissingPathVariableException: Required URI template variable 'productId' for method parameter type Integer is not present
С классическими веб-приложениями я работаю крайне редко (не знаю уж, к счастью или к сожалению), REST в моей практике занимает существенно больше времени. В целом мне без разницы с чем работать)
Catalague-service запускается без проблем, а Manager запускается один раз. Потом выключаешь, перезагружаешь программу, но всё равно ошибка Web server failed to start. Port 8080 was already in use.. Что за???
59:47 при попытке перейти на localhost:8080/catalogue/products/list выдает ошибку: There was an unexpected error (type=Internal Server Error, status=500). java.lang.IllegalArgumentException: URI with undefined scheme ... at ag.selm.manager.client.RestClientProductsRestClient.findAllProducts(RestClientProductsRestClient.java:31) ~[classes/:na] at ag.selm.manager.controller.ProductsController.getProductsList(ProductsController.java:23) ~[classes/:na] и чё с этим делать непонятно
Я - человек простой. Вижу видео от Саши - ставлю лайк) спасибо за туториал)
Второй день, второе видео пройдено, всё очень интересно и познавательно.
Очень крутое качество кода, зная все это, все равно очень полезно смотреть лекции, хотя бы просто для того, чтобы поучиться так грамотно писать) ❤
Саш, как всегда, жирный лайк! Судя по гитхабу на выходе серьезный проект получается. Отличная практика. От души спасибо.
Спасибо! Было полезно узнать про классы ProblemDetail и RestClient, как альтернатива RestTemplate
Наконец-то осилил до конца. Смотрю дальше. Отличный материал.
Отличный материал! Спасибо огромное!
Очень классный гайд, всё на высоте, спасибо
Отличное начало. Супер. Как всегда качественный профессиональный материал.
Спасибо вам большое за такие уроки. После получения в университете и на просторах интернета знаний очень помогает разложить все по полочкам, а также узнать что-то новое. Начинаю первый полностью свой проект по вашему курсу!
Огромное спасибо! Смотрится на одном дыхании!
Эх, жаль, что цикла этого не было еще, когда ваял свой первый проект:) От прыжков по многочисленным граблям бы избавился. Спасибо за труды!
Спасибо за отличный урок!
большое спасибо!) смотрится прям легко
ОЧень годно, спасибо большое, что делитесь
Спасибо за видео! ) Хотела заметить, что для формирования ResponseEntity удобнее (как мне кажется) пользоваться готовым функционалом Spring Hateoas! ;)
Ну, такое)
Спасибо за новый урок
Привет.
Отличный курс, большая работа проделана, виден системный подход. Большой лайк за труды.
Вопрос по блоку возвращения ResponseEntity при обработке ошибок. Заметил, что статус ошибки заполняется два раза, сначала в ProblemDetail, потом как статус ResponseEntity, а problemDetail возвращается в body. В этом есть сакральный смысл (спрашиваю, т.к. есть ощущение, что просто так ничего в роликах не говорится и не делается)? Я у себя сделал вариант
ResponseEntity
.of(problemDetails)
.build()
Вроде работает ))
Да, действительно работает, я упустил этот момент
спасибо большое
Благодарю!
Спасибо Александр и все таки URL и URI это не одно и тоже:)
Классные видео, спору нет, но не разделяю шквал похвалы, тк задача автора не только всё детально показать, но и доходчиво объяснить. На мой взгляд данное видео местами сложновато для понимания новичками, и вызывает не мало вопросов, это на фоне других обучающих материалов по спрингу, где объясняют на примерах необходимого минимума.
Спасибо, братиш 🙂🤘
СПАСИБО за труд !!!
Спасибо автору за отличный материал. У меня вопрос: объясните пожалуйста почему сервис и менеджер крутятся на разных портах, если мы в ямл файлах прописали один порт? Почему в постман мы посылаем запросы на порт 8081, а в браузере видим порт 8080? Вот этот момент до сих пор понять не могу.
В сервисе каталога порт - 8081 github.com/alex-kosarev/sc24/blob/SC24EP02-servlet-api-rest-service/catalogue-service/src/main/resources/application-standalone.yaml
8:00 - Когда речь идет про три разных варианта класса данных Product (для представления, базы данных и самого объекта), то нужны некие преобразователи из одного варианта в другой? Их реализовывать в контроллере и репозитории соответственно? Это получается что-то вроде адаптеров в гексагональной архитектуре?
По идее да, должны быть преобразователи, можно преобразования писать прямо в контроллере и репозитори, можно выносить в отдельные классы. Да, контроллер и репозиторий являются адаптерами в контексте гексагональной архитектуры
Александр, спасибо за видео, с интересом посмотрел. Печалька, что такой полезный класс как ProblemDetail сделали только в 6 версии Spring, не могу его использовать - у нас на проекте 5-я версия)
Самостоятельно описать)
Комент для продвижения
Александр, привет!
22:40 тут не совсем понял в какой момент заполняется аргумент product на 27 строке, и в какой момент отработает метод на строке 21?
Подебажил, как будто бы вызывается перед тем, как вызвать метод контроллера, но как это работает не понятно)
Метод getProduct (21 строка) вызывается перед findProduct (27 строка), его задача - добавить в модель товар, чтобы потом из модели передать товар в аргументы метода findProduct
Добрый день, с чем связан выбор в пользу record, вместо стандартного dto?
Потому что record, на мой взгляд, идеальны для DTO
Привет, спасибо за видос) Вопрос такой, почему просто не отлавливать в глобальном эксэпшн хэндлере ошибки валидации без BindingResult'а в контроллере?
А откуда взяться исключению?
@@shurik_codes у спринга есть MethodArgumentNotValidException который экстендит bindexception и выбрасывается при фейле валидации
@@shurik_codes Добрый день. Через хэндлер отлавливается, просто нужно в настройках ProblemDetail подключить. К сожалению для ошибок валидации все равно придется в хендлере из BindingResult вытаскивать. Жаль стандарт не включает ошибки валидации и при тесте приходится заморачиваться с проверкой проперти, хотя можно просто расширить ProblemDetail, что мне кажется предпочтительней.
@@shurik_codes Если у нас провалится валидация из Payload, вылетит MethodArgumentNotValidException - его как раз и будем перехватывать. Думаю, не стали заморачиваться с хэндлерами просто для экономии времени, а так конечно, ошибки (и тем более их обработку) обычно не ловят в контроллерах
Я верно понимаю, что ResponseEntity по сути нужен только, если требуется кастомизация статуса? Если достаточно каких-то дефолтных (на успех это 200, а на ошибки не помню, что там спринг по умолчанию использует), то соответственно из методов контроллеров возвращаем просто нужный нам объект?
В целом да
Доброго времени суток! Подскажите, на JDK17 этот код должен работать?
Не тестировал, но вроде ничего из JDK 18+ в проекте не используется
Спасибо за видео, уже становится сложнее всё запоминать из-за большой плотности информации, хоть и не впервой это вижу.
Хоть уроков уже много выпущено, хотелось бы попросить озвучивать горячие клавиши, используемые при разработке, чтобы научиться так же быстро управляться с кодом, и прочие фишки IDEA .
Ещё хотел уточнить, DTO не создаются для упрощения уроков и отсутствия работы с БД(на данный момент) или их создание устаревшая практика?
Исключительно для упрощения уроков, на практике DTO нужны
Чтобы лучше запоминать рекомендую повторять за Александром в своей IDE
@@artyomzolotoverkhov8468 Это-то я несомненно делаю, просто смотреть вообще бестолку) Но по ему опыту, информация более менее железно укладывается только через пол года повторений
Здравствуйте, а зачем нужна проверка является ли BindingResult экземпляром BindException, нельзя сразу выбросить BindException(bindingResult)?
У BindingResult есть и другие реализации, не являющиеся исключением
Здраствуйте!
29:45
Не пойму смысл
if (bindingResult.hasErrors()) {
if (bindingResult instanceof BindException exception) {
throw exception;
} else {
throw new BindException(bindingResult);
}
}
Почему нельзя сократить до
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
В целом можно и так сделать, да, но зачем создавать новое исключение, если можно выбросить уже существующее?
@@shurik_codes Понял, спасибо!
Всем привет, подскажите почему в методе createProduct надо возвращать заголовок ?
Приветствую. Что за плагин стоит и оставляет отметку simple (проценты)?
Code Complexity
Заметил у вас интересную штуку. Вот к примеру в методе мы не находим по id и в блоке if кидаем исключение. Затем отдельный метод "ловец" этого исключения. А не проще ли не кидать эксепшн, а просто в блоке иф делать обращение к методу "ловцу"? Как-то выглядит более прямой такая логика, нет?
Я немного ненаглядно показал, да, но предполагается, что в реальных условиях исключение может возникнуть в нескольких местах, и чтобы везде не писать обработку на месте, таким образом дублируя код, гораздо удобнее написать метод с @ExceptionHandler
Понял, спасибо @@shurik_codes
NoSuchElementException выбрасывается в нескольких местах, и будет странно если в одном мы будем выбрасывать в другом обращаться к методу, тем более, что оно выбрасывается в сервисе и мы оттуда не можем обращаться к контроллеру. Вообще по хорошему создать нужно какой-нибудь рест контроллер эдвайс и там уже хэндлить
❤)
Привет, ты указываешь active profile - standalone на 59 минуте, как это сделать если версия community ?
spring.profiles.active в application.yml
@@shurik_codes
то есть в файле application-standalone.yaml
прописать строчку
spring: profile: active
-------------
да?
@@ЭдуардВолков-к7л не, в файле application.yml, ну или при запуске mvn spring-boot:run -Dspring.profiles.active=standalone но это не точно
@@ЭдуардВолков-к7л
С помощью терминала через cd заходите в директорию вашего сервиса("cd catalogue-service", например) и в терминале пишете:
mvn spring-boot:run -D"spring-boot.run.profiles"=standalone
Спасибо за видео. Но у меня есть вопрос) в Вашем примере объект UpdateProductPayload содержит всего два свойства. Предположим этих свойств гораздо больше и содержать новые данные для обновления могут не все т.е. допускается null значение. Как тогда оптимальней всего реализовать обновление объекта, может есть вариант, как избежать ручной проверки данных в каждом из свойств объекта?
Bean Validation)
@@shurik_codes Не очень понимаю, как это поможет если свойство может быть null, т.е. это значение разрешено, то при сохранении null заменит уже имеющееся значение в бд. А задача сохранить только те значения которые не null.
А, вот про что вопрос. Есть два варианта:
1. Описывать структуры данных для каждого варианта изменения, чтобы они содержали только те свойства, которые могут быть изменены
2. Для каждого варианта изменения описывать свой запрос к БД (специфично для SQL, JPA, Spring Data и т.д.)
Просто мысль вслух: можно попробовать @DynamicUpdate на сущность поставить, а при маппинге из payload в сущность null-ы пропускать (но это уже hibernate на борту подразумевается). Неизмененные поля в SQL не попадут (но потеряется перф, т.к. запрос динамический и не закешируется).
@@ИльяКалендарев-е8ж Посмотрите связку mapStruct и JsonNullable для этих целей.
Если кратко, то в DTO, которые отвечают за создание и обновление сущности нужным полям нужно сделать тип JsonNullable (или не String). Это такая обёртка, которая позволит различать null явный, когда мы хотим, чтобы у сущности поле стало null и null от того, что мы не указали это поле в теле запроса.
Далее при отправке запроса, в случае если это поле не будет указано в теле запроса, то его значение будет null, а если указали и явно указали ему стать null, то этот null будет зашит внутри обёртки
После этого мы настраиваем mapStruct для того, чтобы такие поля корректно мапились при преобразовании DTO в объект
Если интересно, могу скинуть учебный проект, где я такое реализовывал
@shurik_codes также интересно ваше мнение про этот подход!
Привет, а как можно сделать, чтобы можно было скрывать левую боковую панель по бинду??
Alt+1
а если у вас будет время можете показать какой-то реальный пример из жизни несложного проекта на спрингу
А как такие rest-сервисы масштабируются? Просто запускается еще один экземпляр, и нагрузка распределяется балансировщиком? Такая архитектура сервиса - хорошее решение для написания масштабируемых приложений?
Да, масштабирование происходит примерно по такой схеме. Архитектура тоже вполне пойдёт
Спасибо за ролик! С первого ролика не совсем понял, может кто нибудь объяснить почему мы используем NewPayload и UpdatePayload? Можно же вроде Product передавать?
Принцип единственной ответственности, по крайней мере одна из его трактовок. При создании и изменении объекта мы не задаём все свойства, из которых объект состоит. В добавок в разных операциях мы можем изменять разные наборы данных. На практике для каждой операции лучше иметь отдельный класс, описывающий полезную нагрузку.
@@shurik_codes Спасибо понял. А правильно ли я понимаю, что почти в таком же контексте(для не изменения сущности, и отделения ее) для таких целей при работе с базами данных используют DTO?
@@РусланСаматов-ы4с да
вопрос по структуре методов контроллера с ветвлениями, зачем блок else если в блоке if есть return? верификация в контроллере не нарушает SRP?
Про метод с ветвлениями - не понял о каком методе идёт речь. Блок else затем, чтобы выполнить код, если условие в if не сработало.
Валидация выполняется не в контроллере, а в валидаторе силами фреймворка, контроллер обрабатывает результат валидации, так что никакого нарушения SRP здесь нет.
@@shurik_codes про if имел ввиду такую структуру:
if(somePredicate){
return x;
}
return anotherX;
таким образом else нет, и порядок обработки не теряется, но это, наверно, больше к код стайлу относится.
@@SlevySoddik а, ну так да, можно делать
Добрый день! Спасибо за курс. У меня проблема - вроде делаю все также, но вылезает ошибка MissingPathVariableException: Required URI template variable 'productId' for method parameter type Integer is not present
Какой путь и как объявлен аргумент метода, получающий переменную пути?
Появляется на страницах catalogue/products/list и catalogue/products/create, где в пути вообще нет productId
А в контроллере есть метод с аннотацией @ModelAttribute и аргументом, отмеченным аннотацией @PathVariable?
@@shurik_codes Спасибо, поняла ))
А откуда у вас ultimate idea , с community не хочу ходить
Неравнодушные люди помогают обновлять подписку)
На торрентах поищи, тебе же для обучения, никто штраф не предъявит, версию 2022 года точно можно найти
Вам как профессиональному разработчику приятнее с REST или MVC работать? Где проще разработка выходит?)
С классическими веб-приложениями я работаю крайне редко (не знаю уж, к счастью или к сожалению), REST в моей практике занимает существенно больше времени. В целом мне без разницы с чем работать)
Я так понял restclient в manager-app это для взаимодействия с сервисом каталога что бы с клиента туда кидать запросы
да
Не пойму почему не выводит продукт, на выхлопе получается "ID: 0, Название: , Описание: ", но в самом списке товаров id и title корректные.
:(
Возможно, где-то есть ошибки, код к ролику: github.com/alex-kosarev/sc24/tree/SC24EP02-servlet-api-rest-service
@@shurik_codes Спасибо, буду разбираться!
У меня у одной что-ли в BindException нет метода getAllErrors()?
а, все, исправила импорт)) но у меня в toList просто не собирается, требует collect(Collectors.toList()))
Это для JDK 17+, в примере используется JDK 21
@@shurik_codes у меня 17. По идее должно работать. И ещё приложение при запуске каждый раз требует новый порт чтобы развернуться. В чем причина?
Что-то занимает этот порт, возможно, запущенный ранее сервис
Catalague-service запускается без проблем, а Manager запускается один раз. Потом выключаешь, перезагружаешь программу, но всё равно ошибка Web server failed to start. Port 8080 was already in use.. Что за???
Ну, значит, приложение менеджера не выключается корректно
59:47
при попытке перейти на localhost:8080/catalogue/products/list выдает ошибку:
There was an unexpected error (type=Internal Server Error, status=500).
java.lang.IllegalArgumentException: URI with undefined scheme
...
at ag.selm.manager.client.RestClientProductsRestClient.findAllProducts(RestClientProductsRestClient.java:31) ~[classes/:na]
at ag.selm.manager.controller.ProductsController.getProductsList(ProductsController.java:23) ~[classes/:na]
и чё с этим делать непонятно
Посмотреть внимательно на настройки RestClient, судя по ошибке, URL не содержит схемы (http:)
@@shurik_codes uri почему-то выходит вот такой: http/:localhost:8081/catalogue-api/products
@@shurik_codes а где эти настройки найти?
@@shurik_codes всё, нашел ошибку - было http//: вместо 😃
Спасибо!