Skip to content

Conversation

@ViktorSalk
Copy link
Owner

Создал пакет model. Реализовал в нем классы: Film и User. Это классы — модели данных приложения. Создал два класса-контроллера. FilmController для фильмов, и UserController для пользователей. Создал UserNotFoundException, ValidationException, GlobalExceptionHandler для валидации. Создал тесты.

Создал пакет model. Реализовал в нем классы: Film и User. Это классы — модели данных приложения.
Создал два класса-контроллера. FilmController для фильмов, и UserController для пользователей.
Создал UserNotFoundException, ValidationException, GlobalExceptionHandler для валидации.
Создал тесты.
Copy link

@afomkina afomkina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Очень рекоммендую попробовать сделать дополнительное задание, так как сприговая валидация очень популярна в коммерческой разработке и никто не пишет валидацию руками. Она очень сильно сокращает код и избавляет от ошибок "невнимательности". Вот отлчиная статься про аннотации валидации


final Gson gson = new Gson();
final Scanner scanner = new Scanner(System.in);
System.out.print("Введите JSON => ");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Код со строк 17-27 нужно удалить

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. в FilmorateApplication удалил лишний код.

}

@GetMapping
public Collection<Film> findAll() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше возвращать не Collection менее абстрактный интерфейс - List, так мы точно будем знать какой тип коллекции ожидать в ответе

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использовал более конкретный тип List вместо Collection, сделав API более понятным и защитив его от возможных изменений извне.

}

@GetMapping
public Collection<User> findAll() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше возвращать не Collection менее абстрактный интерфейс - List, так мы точно будем знать какой тип коллекции ожидать в ответе

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использовал более конкретный тип List вместо Collection, сделав API более понятным и защитив его от возможных изменений извне.

public class GlobalExceptionHandler {

@ExceptionHandler(ValidationException.class)
public ResponseEntity<Map<String, String>> handleValidationException(ValidationException ex) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Вместо Map<String, String> лучше взвращать объект ErrorResponse, в котором будет поле error, куда нужно будет записывать причину ошибки
  • Можно сделать код еще лаконичнее, избавившись от обертки ResponseEntity. Для этого нужно просто добавить аннотацию @ResponseStatus(HttpStatus. BAD_REQUEST) над методом, а в самом методе возвращать ErrorResponse.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вместо Map<String, String> использовал @ResponseStatus(HttpStatus. BAD_REQUEST), создав ErrorResponse.

@Data
public class Film {

Long id;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У всех полей класса должен быть модификатор доступа

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Указал модификатор доступа для полей класса, для геттеров и сеттеров при аннотации использовал приватный.


@Data
public class User {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У всех полей класса должен быть модификатор доступа

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Указал модификатор доступа для полей класса, для геттеров и сеттеров при аннотации использовал приватный.

…ms-users

1) в FilmorateApplication удалил лишний код.
2) в FilmController и UserController использовал более конкретный тип List вместо Collection сделав API более понятным и защищая от возможных изменений извне.
3) в GlobalExceptionHandler вместо Map<String, String> использовал @ResponseStatus(HttpStatus. BAD_REQUEST) создав ErrorResponse.
4) в Film и User указал модификатор доступа для полей класса, для геттеров и сеттеров при аннотации, использовал приватный.
5) Реализовал дополнительное задание используя спринговую валидацию, С помощью аннотаций задав ограничения, которые будут проверяться автоматически: подредактировал Film и User, FilmController и UserController, FilmControllerTests и UserControllerTests. Чтобы Spring не только преобразовал тело запроса в соответствующий класс, но и проверил корректность переданных данных, вместе с аннотацией @RequestBody использовал аннотацию @Valid.
@ViktorSalk
Copy link
Owner Author

Очень рекомендую попробовать выполнить дополнительное задание, так как сприговая валидация очень популярна в коммерческой разработке, и никто не пишет валидацию вручную. Она значительно сокращает код и избавляет от «невнимательных» ошибок. Вот отличная статья об аннотациях валидации

Спасибо) Реализовал дополнительное задание, используя проверку Spring, с помощью аннотаций задав ограничения, которые будут проверяться автоматически: подредактировал классы Film и User, контроллеры FilmController и UserController, тесты FilmControllerTests и UserControllerTests. Чтобы Spring не только преобразовывал тело запроса в соответствующий класс, но и проверял корректность переданных данных, вместе с аннотацией @RequestBody использовал аннотацию @Valid.

}

@PostMapping
public ResponseEntity<Film> createFilm(@Valid @RequestBody Film film) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь лучше тоже использовать @ResponseStatus(HttpStatus.CREATED), чтобы избавиться от дополнительной обертки ResponseEntity и просто возвращать Film

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В FilmController в createFilm использовал @ResponseStatus(HttpStatus.CREATED), чтобы избавиться от дополнительной обертки.

}

@PutMapping
public ResponseEntity<Film> updateFilm(@Valid @RequestBody Film film) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нужно использовать @ResponseStatus

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В FilmController в updateFilm использовал @ResponseStatus(HttpStatus.ОК).

public class ErrorResponse {
private final String error;

public ErrorResponse(String error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Data включает в себя аннотацию @RequiredArgsConstructor, поэтому явно его объявлять не нужно

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В ErrorResponse убрал явное объявление.


@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleUserNotFoundException(UserNotFoundException ex) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше явно прописать название переменной, как exception. Без сокращений код выглядит более читаемо

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В GlobalExceptionHandler убрал сокращения.

private Long id;

@NotNull(message = "Название фильма не может быть пустым")
@NotBlank(message = "Название фильма не может быть пустым")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На самом деле @NotBlank содержет проверку на null, поэтому добавлять дополнительно @NotNull не нужно

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо) В Film убрал лишние @NotNull.


@NotNull(message = "Описание фильма не может быть пустым")
@NotBlank(message = "Описание фильма не может быть пустым")
private String description;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чтобы полностью избавиться от ручной валидации, нужно добавить @Size

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В Film description добавил @SiZe на 200 символов.

private String description;

@NotNull(message = "Дата выхода не может быть пустой")
private LocalDate releaseDate;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно реализовать кастомную аннотацию валидации, чтобы полностью отказаться от ручной валидации.
Вот отличная статья, в которой подробно описано, как это можно сделать

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо) Реализовал кастомную аннотацию валидации ReleaseDateValidation для Film.

private LocalDate releaseDate;

@NotNull(message = "Длительность не может быть пустой")
private Integer duration;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PositiveOrZero проверяет, что число больше либо равно нулю

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В Film duration добавил @PositiveOrZero на длительность фильма.

user.setId(getNextId());

// Проверка на существующий логин
if (users.values().stream().anyMatch(existingUser -> existingUser.getLogin().equals(user.getLogin()))) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Дополнительные оптимизации
Чтобы проверка почты проходила за O(1), нужно завести Set, куда при добавлении нового пользователя мы будем класть его email. Соответственно, перед тем как добавить пользователя, нужно будет посмотреть, есть ли уже в сете подобный email, если есть - то пользователь с таким email уже существует.

Так как поиск и вставка в сете работают в среднем за O(1), то наш алгоритм поиска одинаковых email тоже будет работать в среднем за O(1), а это гораздо быстрее, чем пройтись по всем элементам списка O(n)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В UserController провел дополнительную оптимизацию алгоритма поиска, чтобы проверка почты проходила быстрее, за O(1).

throw new ValidationException("Ошибка. Попытка добавить логин, который уже существует");
}

if (user.getName() == null || user.getName().isBlank()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Эту проверку можно перенести в конструктор класса User, тогда поле будет автоматически проставляться сразу при создании экземпляра класса

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Перенес проверку из UserController в конструктор класса User, создав метод setLogin.

…us.CREATED), чтобы избавиться от дополнительной обертки.

2) В FilmController в updateFilm использовал @ResponseStatus(HttpStatus.ОК).
3) В ErrorResponse убрал явное объявление.
4) В GlobalExceptionHandler убрал сокращения.
5) В Film убрал лишние @NotNull.
6) В Film description добавил @SiZe на 200 символов.
7) Реализовал кастомную аннотацию валидации ReleaseDateValidation для Film.
8) В Film duration добавил @PositiveOrZero на длительность фильма.
9) В UserController провел дополнительную оптимизацию алгоритма поиска, чтобы проверка почты проходила быстрее, за O(1).
10) Перенес проверку init из UserController в конструктор класса User.
11) В User убрал лишние @NotNull.
throw new UserNotFoundException("Фильм с id = " + film.getId() + " не найден");
}

private void filmValidator(@RequestBody Film film) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот метод нужно удалить, так как валидация теперь работает с помощью аннотаций

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Удалил filmValidator, дублирующий валидацию.

@ViktorSalk ViktorSalk merged commit 9a4a51d into main Jan 5, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants