SC24EP02 Разработка REST-сервиса - Разработка проектов со Spring

Поделиться
HTML-код
  • Опубликовано: 26 сен 2024
  • Во втором ролике цикла я рассказываю об особенностях разработки REST-сервисов с помощью Spring WebMVC.
    В цикле роликов "Разработка проектов со Spring" я рассказываю на простых примерах о процессе разработки веб-приложений и REST-сервисов на языке программирования Java с использованием экосистемы Spring. Данный цикл охватывает разработку классических и реактивных проектов, вопросы их сопровождения, такие как документация и мониторинг, адаптацию их к облачной инфраструктуре и процесс их развёртывания в Docker и Kubernetes.
    Репозиторий проекта: github.com/ale...
    Мои ресурсы:
    - Сайт: alexkosarev.name
    - Канал на RUclips: / @shurik_codes
    - Канал в Telegram: t.me/+TZCuO38v...
    - Группа для обсуждений в Telegram: t.me/+UFAkw187...
    - Паблик в VK: shurik....
    - Канал в Дзене: dzen.ru/shurik...
    - Канал на Rutube: rutube.ru/chan...
    - Страница в Boosty: boosty.to/akos...
    Поддержать проект:
    - Доны в VK: donut/s...
    - Донаты в Boosty: boosty.to/akos...
    - Через Tinkoff: www.tinkoff.ru...
    #java #spring #rest #howto

Комментарии • 116

  • @СергейЛымарь-э2н
    @СергейЛымарь-э2н 6 месяцев назад +9

    Я - человек простой. Вижу видео от Саши - ставлю лайк) спасибо за туториал)

  • @ejatohvee
    @ejatohvee 2 месяца назад +1

    Спасибо вам большое за такие уроки. После получения в университете и на просторах интернета знаний очень помогает разложить все по полочкам, а также узнать что-то новое. Начинаю первый полностью свой проект по вашему курсу!

  • @Edu-wi3il
    @Edu-wi3il 6 месяцев назад +5

    Спасибо! Было полезно узнать про классы ProblemDetail и RestClient, как альтернатива RestTemplate

  • @tekkaruwer8789
    @tekkaruwer8789 5 месяцев назад +2

    Очень крутое качество кода, зная все это, все равно очень полезно смотреть лекции, хотя бы просто для того, чтобы поучиться так грамотно писать) ❤

  • @Devivl
    @Devivl 6 месяцев назад +6

    Саш, как всегда, жирный лайк! Судя по гитхабу на выходе серьезный проект получается. Отличная практика. От души спасибо.

  • @artemv5226
    @artemv5226 6 месяцев назад +1

    Эх, жаль, что цикла этого не было еще, когда ваял свой первый проект:) От прыжков по многочисленным граблям бы избавился. Спасибо за труды!

  • @Lina-sl9js
    @Lina-sl9js 3 месяца назад +1

    Спасибо за видео! ) Хотела заметить, что для формирования ResponseEntity удобнее (как мне кажется) пользоваться готовым функционалом Spring Hateoas! ;)

  • @Константин-ы9к
    @Константин-ы9к 6 месяцев назад +3

    Отличное начало. Супер. Как всегда качественный профессиональный материал.

  • @timur.k
    @timur.k 3 месяца назад +1

    Спасибо за отличный урок!

  • @Константин-ы9к
    @Константин-ы9к 5 месяцев назад +1

    Наконец-то осилил до конца. Смотрю дальше. Отличный материал.

  • @alekseizhitenev6020
    @alekseizhitenev6020 3 месяца назад +1

    большое спасибо!) смотрится прям легко

  • @itmaker1821
    @itmaker1821 6 месяцев назад +1

    Очень классный гайд, всё на высоте, спасибо

  • @partiec6065
    @partiec6065 3 месяца назад +1

    Спасибо, братиш 🙂🤘

  • @vladislavchaplin894
    @vladislavchaplin894 6 месяцев назад +1

    ОЧень годно, спасибо большое, что делитесь

  • @АлександрТяпкин-п2ц
    @АлександрТяпкин-п2ц 6 месяцев назад +1

    Спасибо за новый урок

  • @ровойт
    @ровойт 6 месяцев назад +2

    спасибо большое

  • @shluhogon_42
    @shluhogon_42 6 месяцев назад +2

    Благодарю!

  • @webicode
    @webicode 4 месяца назад

    СПАСИБО за труд !!!

  • @FrendlyFrend-g6j
    @FrendlyFrend-g6j 6 месяцев назад +1

    Спасибо Александр и все таки URL и URI это не одно и тоже:)

  • @romanovichihin2429
    @romanovichihin2429 3 месяца назад

    Комент для продвижения

  • @ГенрихАвдеев-ь9з
    @ГенрихАвдеев-ь9з 6 месяцев назад +4

    Привет.
    Отличный курс, большая работа проделана, виден системный подход. Большой лайк за труды.
    Вопрос по блоку возвращения ResponseEntity при обработке ошибок. Заметил, что статус ошибки заполняется два раза, сначала в ProblemDetail, потом как статус ResponseEntity, а problemDetail возвращается в body. В этом есть сакральный смысл (спрашиваю, т.к. есть ощущение, что просто так ничего в роликах не говорится и не делается)? Я у себя сделал вариант
    ResponseEntity
    .of(problemDetails)
    .build()
    Вроде работает ))

    • @shurik_codes
      @shurik_codes  6 месяцев назад +2

      Да, действительно работает, я упустил этот момент

  • @aleksey2793
    @aleksey2793 Месяц назад

    8:00 - Когда речь идет про три разных варианта класса данных Product (для представления, базы данных и самого объекта), то нужны некие преобразователи из одного варианта в другой? Их реализовывать в контроллере и репозитории соответственно? Это получается что-то вроде адаптеров в гексагональной архитектуре?

    • @shurik_codes
      @shurik_codes  Месяц назад +1

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

  • @tusman4ik
    @tusman4ik 6 месяцев назад +1

    ❤)

  • @o.sandman
    @o.sandman 6 месяцев назад +1

    Александр, спасибо за видео, с интересом посмотрел. Печалька, что такой полезный класс как ProblemDetail сделали только в 6 версии Spring, не могу его использовать - у нас на проекте 5-я версия)

    • @shurik_codes
      @shurik_codes  6 месяцев назад +2

      Самостоятельно описать)

  • @Hocorend
    @Hocorend 4 месяца назад +1

    Спасибо за видео, уже становится сложнее всё запоминать из-за большой плотности информации, хоть и не впервой это вижу.
    Хоть уроков уже много выпущено, хотелось бы попросить озвучивать горячие клавиши, используемые при разработке, чтобы научиться так же быстро управляться с кодом, и прочие фишки IDEA .
    Ещё хотел уточнить, DTO не создаются для упрощения уроков и отсутствия работы с БД(на данный момент) или их создание устаревшая практика?

    • @shurik_codes
      @shurik_codes  4 месяца назад

      Исключительно для упрощения уроков, на практике DTO нужны

    • @artyomzolotoverkhov8468
      @artyomzolotoverkhov8468 4 месяца назад

      Чтобы лучше запоминать рекомендую повторять за Александром в своей IDE

    • @Hocorend
      @Hocorend 4 месяца назад

      @@artyomzolotoverkhov8468 Это-то я несомненно делаю, просто смотреть вообще бестолку) Но по ему опыту, информация более менее железно укладывается только через пол года повторений

  • @alexandr6055
    @alexandr6055 6 месяцев назад +1

    Заметил у вас интересную штуку. Вот к примеру в методе мы не находим по id и в блоке if кидаем исключение. Затем отдельный метод "ловец" этого исключения. А не проще ли не кидать эксепшн, а просто в блоке иф делать обращение к методу "ловцу"? Как-то выглядит более прямой такая логика, нет?

    • @shurik_codes
      @shurik_codes  6 месяцев назад

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

    • @alexandr6055
      @alexandr6055 6 месяцев назад

      Понял, спасибо ​@@shurik_codes

    • @ji1ja
      @ji1ja 6 месяцев назад

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

  • @АлександрЛапин-б7р
    @АлександрЛапин-б7р 2 месяца назад +1

    Огромное спасибо! Смотрится на одном дыхании!

  • @ровойт
    @ровойт 5 месяцев назад

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

  • @oleksandrhavryush9250
    @oleksandrhavryush9250 5 месяцев назад +1

    Спасибо автору за отличный материал. У меня вопрос: объясните пожалуйста почему сервис и менеджер крутятся на разных портах, если мы в ямл файлах прописали один порт? Почему в постман мы посылаем запросы на порт 8081, а в браузере видим порт 8080? Вот этот момент до сих пор понять не могу.

    • @shurik_codes
      @shurik_codes  5 месяцев назад

      В сервисе каталога порт - 8081 github.com/alex-kosarev/sc24/blob/SC24EP02-servlet-api-rest-service/catalogue-service/src/main/resources/application-standalone.yaml

  • @pavelgurevich4018
    @pavelgurevich4018 5 месяцев назад

    Всем привет, подскажите почему в методе createProduct надо возвращать заголовок ?

  • @trfguiikbfdyjkk
    @trfguiikbfdyjkk 3 месяца назад

    Здравствуйте, а зачем нужна проверка является ли BindingResult экземпляром BindException, нельзя сразу выбросить BindException(bindingResult)?

    • @shurik_codes
      @shurik_codes  3 месяца назад

      У BindingResult есть и другие реализации, не являющиеся исключением

  • @aleksey2793
    @aleksey2793 2 месяца назад

    А как такие rest-сервисы масштабируются? Просто запускается еще один экземпляр, и нагрузка распределяется балансировщиком? Такая архитектура сервиса - хорошее решение для написания масштабируемых приложений?

    • @shurik_codes
      @shurik_codes  2 месяца назад +1

      Да, масштабирование происходит примерно по такой схеме. Архитектура тоже вполне пойдёт

  • @viewer_evgeniy
    @viewer_evgeniy 6 месяцев назад

    Я верно понимаю, что ResponseEntity по сути нужен только, если требуется кастомизация статуса? Если достаточно каких-то дефолтных (на успех это 200, а на ошибки не помню, что там спринг по умолчанию использует), то соответственно из методов контроллеров возвращаем просто нужный нам объект?

  • @СергейЗаря-х9ь
    @СергейЗаря-х9ь Месяц назад

    Добрый день, с чем связан выбор в пользу record, вместо стандартного dto?

    • @shurik_codes
      @shurik_codes  Месяц назад

      Потому что record, на мой взгляд, идеальны для DTO

  • @non_holy6286
    @non_holy6286 Месяц назад

    Александр, привет!
    22:40 тут не совсем понял в какой момент заполняется аргумент product на 27 строке, и в какой момент отработает метод на строке 21?

    • @non_holy6286
      @non_holy6286 Месяц назад

      Подебажил, как будто бы вызывается перед тем, как вызвать метод контроллера, но как это работает не понятно)

    • @shurik_codes
      @shurik_codes  9 дней назад

      Метод getProduct (21 строка) вызывается перед findProduct (27 строка), его задача - добавить в модель товар, чтобы потом из модели передать товар в аргументы метода findProduct

  • @РусланСаматов-ы4с
    @РусланСаматов-ы4с 6 месяцев назад

    Спасибо за ролик! С первого ролика не совсем понял, может кто нибудь объяснить почему мы используем NewPayload и UpdatePayload? Можно же вроде Product передавать?

    • @shurik_codes
      @shurik_codes  6 месяцев назад +1

      Принцип единственной ответственности, по крайней мере одна из его трактовок. При создании и изменении объекта мы не задаём все свойства, из которых объект состоит. В добавок в разных операциях мы можем изменять разные наборы данных. На практике для каждой операции лучше иметь отдельный класс, описывающий полезную нагрузку.

    • @РусланСаматов-ы4с
      @РусланСаматов-ы4с 6 месяцев назад

      @@shurik_codes Спасибо понял. А правильно ли я понимаю, что почти в таком же контексте(для не изменения сущности, и отделения ее) для таких целей при работе с базами данных используют DTO?

    • @shurik_codes
      @shurik_codes  6 месяцев назад

      @@РусланСаматов-ы4с да

  • @denisskyter4526
    @denisskyter4526 6 месяцев назад +1

    А откуда у вас ultimate idea , с community не хочу ходить

    • @shurik_codes
      @shurik_codes  6 месяцев назад +4

      Неравнодушные люди помогают обновлять подписку)

    • @Hocorend
      @Hocorend 4 месяца назад

      На торрентах поищи, тебе же для обучения, никто штраф не предъявит, версию 2022 года точно можно найти

  • @denisskyter4526
    @denisskyter4526 6 месяцев назад

    Я так понял restclient в manager-app это для взаимодействия с сервисом каталога что бы с клиента туда кидать запросы

  • @sdhfdsfh2413
    @sdhfdsfh2413 6 месяцев назад

    Привет, а как можно сделать, чтобы можно было скрывать левую боковую панель по бинду??

  • @bolekrus
    @bolekrus 5 месяцев назад

    Catalague-service запускается без проблем, а Manager запускается один раз. Потом выключаешь, перезагружаешь программу, но всё равно ошибка Web server failed to start. Port 8080 was already in use.. Что за???

    • @shurik_codes
      @shurik_codes  5 месяцев назад

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

  • @НатаСметанова
    @НатаСметанова 5 месяцев назад

    Добрый день! Спасибо за курс. У меня проблема - вроде делаю все также, но вылезает ошибка MissingPathVariableException: Required URI template variable 'productId' for method parameter type Integer is not present

    • @shurik_codes
      @shurik_codes  5 месяцев назад

      Какой путь и как объявлен аргумент метода, получающий переменную пути?

    • @НатаСметанова
      @НатаСметанова 5 месяцев назад

      Появляется на страницах catalogue/products/list и catalogue/products/create, где в пути вообще нет productId

    • @shurik_codes
      @shurik_codes  5 месяцев назад

      А в контроллере есть метод с аннотацией @ModelAttribute и аргументом, отмеченным аннотацией @PathVariable?

    • @НатаСметанова
      @НатаСметанова 5 месяцев назад

      @@shurik_codes Спасибо, поняла ))

  • @ladamira3477
    @ladamira3477 5 месяцев назад +1

    У меня у одной что-ли в BindException нет метода getAllErrors()?

    • @ladamira3477
      @ladamira3477 5 месяцев назад

      а, все, исправила импорт)) но у меня в toList просто не собирается, требует collect(Collectors.toList()))

    • @shurik_codes
      @shurik_codes  5 месяцев назад +1

      Это для JDK 17+, в примере используется JDK 21

    • @ladamira3477
      @ladamira3477 5 месяцев назад

      ​@@shurik_codes у меня 17. По идее должно работать. И ещё приложение при запуске каждый раз требует новый порт чтобы развернуться. В чем причина?

    • @shurik_codes
      @shurik_codes  5 месяцев назад

      Что-то занимает этот порт, возможно, запущенный ранее сервис

  • @ИванГубарев-к4ь
    @ИванГубарев-к4ь 3 месяца назад

    Доброго времени суток! Подскажите, на JDK17 этот код должен работать?

    • @shurik_codes
      @shurik_codes  3 месяца назад

      Не тестировал, но вроде ничего из JDK 18+ в проекте не используется

  • @ji1ja
    @ji1ja 6 месяцев назад

    Привет, спасибо за видос) Вопрос такой, почему просто не отлавливать в глобальном эксэпшн хэндлере ошибки валидации без BindingResult'а в контроллере?

    • @shurik_codes
      @shurik_codes  6 месяцев назад

      А откуда взяться исключению?

    • @ji1ja
      @ji1ja 6 месяцев назад

      @@shurik_codes у спринга есть MethodArgumentNotValidException который экстендит bindexception и выбрасывается при фейле валидации

    • @rainrainov4495
      @rainrainov4495 3 месяца назад

      @@shurik_codes Добрый день. Через хэндлер отлавливается, просто нужно в настройках ProblemDetail подключить. К сожалению для ошибок валидации все равно придется в хендлере из BindingResult вытаскивать. Жаль стандарт не включает ошибки валидации и при тесте приходится заморачиваться с проверкой проперти, хотя можно просто расширить ProblemDetail, что мне кажется предпочтительней.

    • @hurricane-rus
      @hurricane-rus 3 месяца назад

      @@shurik_codes Если у нас провалится валидация из Payload, вылетит MethodArgumentNotValidException - его как раз и будем перехватывать. Думаю, не стали заморачиваться с хэндлерами просто для экономии времени, а так конечно, ошибки (и тем более их обработку) обычно не ловят в контроллерах

  • @SlevySoddik
    @SlevySoddik 6 месяцев назад

    вопрос по структуре методов контроллера с ветвлениями, зачем блок else если в блоке if есть return? верификация в контроллере не нарушает SRP?

    • @shurik_codes
      @shurik_codes  6 месяцев назад

      Про метод с ветвлениями - не понял о каком методе идёт речь. Блок else затем, чтобы выполнить код, если условие в if не сработало.
      Валидация выполняется не в контроллере, а в валидаторе силами фреймворка, контроллер обрабатывает результат валидации, так что никакого нарушения SRP здесь нет.

    • @SlevySoddik
      @SlevySoddik 6 месяцев назад

      @@shurik_codes про if имел ввиду такую структуру:
      if(somePredicate){
      return x;
      }
      return anotherX;
      таким образом else нет, и порядок обработки не теряется, но это, наверно, больше к код стайлу относится.

    • @shurik_codes
      @shurik_codes  6 месяцев назад

      @@SlevySoddik а, ну так да, можно делать

  • @sermaz-blg
    @sermaz-blg 5 месяцев назад

    Приветствую. Что за плагин стоит и оставляет отметку simple (проценты)?

  • @ИльяКалендарев-е8ж
    @ИльяКалендарев-е8ж 6 месяцев назад

    Спасибо за видео. Но у меня есть вопрос) в Вашем примере объект UpdateProductPayload содержит всего два свойства. Предположим этих свойств гораздо больше и содержать новые данные для обновления могут не все т.е. допускается null значение. Как тогда оптимальней всего реализовать обновление объекта, может есть вариант, как избежать ручной проверки данных в каждом из свойств объекта?

    • @shurik_codes
      @shurik_codes  6 месяцев назад

      Bean Validation)

    • @ИльяКалендарев-е8ж
      @ИльяКалендарев-е8ж 6 месяцев назад

      @@shurik_codes Не очень понимаю, как это поможет если свойство может быть null, т.е. это значение разрешено, то при сохранении null заменит уже имеющееся значение в бд. А задача сохранить только те значения которые не null.

    • @shurik_codes
      @shurik_codes  6 месяцев назад

      А, вот про что вопрос. Есть два варианта:
      1. Описывать структуры данных для каждого варианта изменения, чтобы они содержали только те свойства, которые могут быть изменены
      2. Для каждого варианта изменения описывать свой запрос к БД (специфично для SQL, JPA, Spring Data и т.д.)

    • @javac
      @javac 6 месяцев назад

      Просто мысль вслух: можно попробовать @DynamicUpdate на сущность поставить, а при маппинге из payload в сущность null-ы пропускать (но это уже hibernate на борту подразумевается). Неизмененные поля в SQL не попадут (но потеряется перф, т.к. запрос динамический и не закешируется).

    • @bazilval
      @bazilval 5 месяцев назад

      @@ИльяКалендарев-е8ж Посмотрите связку mapStruct и JsonNullable для этих целей.
      Если кратко, то в DTO, которые отвечают за создание и обновление сущности нужным полям нужно сделать тип JsonNullable (или не String). Это такая обёртка, которая позволит различать null явный, когда мы хотим, чтобы у сущности поле стало null и null от того, что мы не указали это поле в теле запроса.
      Далее при отправке запроса, в случае если это поле не будет указано в теле запроса, то его значение будет null, а если указали и явно указали ему стать null, то этот null будет зашит внутри обёртки
      После этого мы настраиваем mapStruct для того, чтобы такие поля корректно мапились при преобразовании DTO в объект
      Если интересно, могу скинуть учебный проект, где я такое реализовывал
      @shurik_codes также интересно ваше мнение про этот подход!

  • @ЭдуардВолков-к7л
    @ЭдуардВолков-к7л 4 месяца назад

    Привет, ты указываешь active profile - standalone на 59 минуте, как это сделать если версия community ?

    • @shurik_codes
      @shurik_codes  4 месяца назад +1

      spring.profiles.active в application.yml

    • @ЭдуардВолков-к7л
      @ЭдуардВолков-к7л 4 месяца назад

      @@shurik_codes
      то есть в файле application-standalone.yaml
      прописать строчку
      spring: profile: active
      -------------
      да?

    • @shurik_codes
      @shurik_codes  4 месяца назад

      @@ЭдуардВолков-к7л не, в файле application.yml, ну или при запуске mvn spring-boot:run -Dspring.profiles.active=standalone но это не точно

    • @GyMaNoiD-ql3wb
      @GyMaNoiD-ql3wb 4 месяца назад

      @@ЭдуардВолков-к7л
      С помощью терминала через cd заходите в директорию вашего сервиса("cd catalogue-service", например) и в терминале пишете:
      mvn spring-boot:run -D"spring-boot.run.profiles"=standalone

  • @odtour
    @odtour 9 дней назад

    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]
    и чё с этим делать непонятно

    • @shurik_codes
      @shurik_codes  8 дней назад

      Посмотреть внимательно на настройки RestClient, судя по ошибке, URL не содержит схемы (http:)

    • @odtour
      @odtour 8 дней назад

      @@shurik_codes uri почему-то выходит вот такой: http/:localhost:8081/catalogue-api/products

    • @odtour
      @odtour 8 дней назад

      @@shurik_codes а где эти настройки найти?

    • @odtour
      @odtour 8 дней назад

      @@shurik_codes всё, нашел ошибку - было http//: вместо 😃

  • @Boraldan
    @Boraldan 6 месяцев назад +1

    Спасибо!

  • @svyatoiambrozii
    @svyatoiambrozii 5 месяцев назад +1

    Вам как профессиональному разработчику приятнее с REST или MVC работать? Где проще разработка выходит?)

    • @shurik_codes
      @shurik_codes  5 месяцев назад +2

      С классическими веб-приложениями я работаю крайне редко (не знаю уж, к счастью или к сожалению), REST в моей практике занимает существенно больше времени. В целом мне без разницы с чем работать)

  • @akalavan5395
    @akalavan5395 5 месяцев назад +1

    Второй день, второе видео пройдено, всё очень интересно и познавательно.

  • @romoshi
    @romoshi 5 месяцев назад

    Не пойму почему не выводит продукт, на выхлопе получается "ID: 0, Название: , Описание: ", но в самом списке товаров id и title корректные.
    :(

    • @shurik_codes
      @shurik_codes  5 месяцев назад +1

      Возможно, где-то есть ошибки, код к ролику: github.com/alex-kosarev/sc24/tree/SC24EP02-servlet-api-rest-service

    • @romoshi
      @romoshi 5 месяцев назад

      @@shurik_codes Спасибо, буду разбираться!