JavaRush /Курси /Модуль 5. Spring /Лекція 155: Налаштування контролерів і сервісів для оброб...

Лекція 155: Налаштування контролерів і сервісів для обробки запитів

Модуль 5. Spring
Рівень 25 , Лекція 4
Відкрита

Вивчення Spring MVC нагадує процес "очеловічення" робота: ти додаєш вуха (контролери для прийому запитів), мозок (сервіси для обробки логіки) і руки (репозиторії для роботи з базою даних). Сьогодні ми зосередимося на вухах і мозку. Репозиторії вже були створені раніше, тому варто доручити їм безпосередню взаємодію з базою даних.


Що таке контролери і сервіси в Spring MVC?

Контролери отримують вхідні запити і вирішують, як їх обробити, передаючи задачі сервісам. Сервіси, в свою чергу, реалізують бізнес-логіку, а якщо потрібно, звертаються до репозиторіїв (аналог складу з інгредієнтами).

Controller:

  • Відповідає за маршрутизацію запитів до правильного обробника.
  • Обробляє вхідні дані (наприклад, JSON з HTTP-запиту).
  • Повертає відповідь клієнту.

Service:

  • Виконує основну бізнес-логіку.
  • Декларує і/або використовує методи репозиторія для роботи з базою даних.
  • Гарантує, що функціональність є ізольованою і повторно використовуваною.

Створення REST-контролерів: обробка вхідних HTTP-запитів

REST-контролери — це серце твого вебзастосунку. Вони приймають HTTP-запити (GET, POST, PUT, DELETE) і повертають відповіді. Для їх створення використовується анотація @RestController, а ендпоінти задаються за допомогою анотацій @RequestMapping, @GetMapping, @PostMapping тощо.

Приклад мінімального контролера:


@RestController // Каже Spring, що цей клас — контролер
@RequestMapping("/api/v1/users") // Визначає базовий шлях для всіх ендпоінтів у цьому контролері
public class UserController {

    @GetMapping("/{id}") // Обробка GET-запитів за шляхом /api/v1/users/{id}
    public String getUserById(@PathVariable Long id) {
        return "Користувач з ID: " + id; // Повертає рядок з ID користувача
    }

    @PostMapping // Обробка POST-запитів на /api/v1/users
    public String createUser(@RequestBody String userName) {
        return "Створено користувача: " + userName; // Повертає повідомлення
    }
}
  • @RestController: вказує, що повернені дані будуть у форматі JSON або XML.
  • @RequestMapping: встановлює базовий URL для всіх обробників у цьому контролері.
  • @GetMapping, @PostMapping і т.д.: налаштовують обробку певних HTTP-методів (GET, POST, DELETE, PUT).
  • @PathVariable: дозволяє витягати параметри з частини URL.
  • @RequestBody: читає тіло запиту, перетворюючи його з JSON в об'єкт Java.

Розробка сервісного шару для бізнес-логіки

Як ми вже зрозуміли, контролери не повинні містити бізнес-логіку — це зона відповідальності сервісів. Вони виступають містком між контролерами і репозиторіями. Сервіси створюють як компоненти Spring з використанням анотації @Service.

Приклад сервісу:


@Service // Позначає клас як Spring-компонент
public class UserService {

    private final UserRepository userRepository; // Репозиторій для взаємодії з базою даних

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id) // Отримуємо користувача з бази
                .orElseThrow(() -> new RuntimeException("Користувача не знайдено!")); // Помилка, якщо користувача не знайдено
    }

    public void createUser(User user) {
        userRepository.save(user); // Зберігаємо користувача в базу даних
    }
}
  • @Service: повідомляє Spring, що це компонент, що містить бізнес-логіку.
  • UserRepository: автоматично інжектиться через конструктор (Dependency Injection, DI).
  • findById і save: стандартні методи репозиторія для CRUD-операцій.

Інтеграція контролерів з сервісами

Контролери і сервіси працюють у зв'язці. Контролер викликає методи сервісу для обробки запитів. Для цього ми передаємо сервіс як залежність у контролер через DI.

Приклад інтеграції:


@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService; // Впровадження залежності
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id); // Виклик методу сервісу
    }

    @PostMapping
    public void createUser(@RequestBody User user) {
        userService.createUser(user); // Виклик методу сервісу
    }
}

Покращення структури проєкту через використання патернів

Щоб твій проєкт був зрозумілим і підтримуваним, варто слідувати таким патернам:

  • Controller-Service-Repository: ніколи не змішуй логіку з контролерів і сервісів.
  • DTO (Data Transfer Object): використовуй DTO для передачі даних між шарами. Це особливо важливо, коли структура сутності бази даних і даних, яких очікує клієнт, відрізняються.
  • Розподіл обов'язків: контролери займаються тільки запитами, сервіси реалізують логіку, а репозиторії працюють з базою даних.

Покроковий приклад розробки

Створимо просту функціональність для роботи з користувачами.

1. Сутність:


@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // Геттери і сеттери...
}

2. Репозиторій:


@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // Можливі додаткові кастомні запити
}

3. Сервіс:


@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow(() -> new RuntimeException("Користувача не знайдено"));
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public void saveUser(User user) {
        userRepository.save(user);
    }
}

4. Контролер:


@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUserById(id));
    }

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }

    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody User user) {
        userService.saveUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body("Користувача успішно створено");
    }
}

Типові помилки та способи їх усунення

  1. Змішування логіки в контролері: не пиши бізнес-логіку в контролері. Сервіси краще для цього підходять.
  2. Відсутність обробки помилок: переконайся, що обробляєш виключення (наприклад, через @ExceptionHandler або @ControllerAdvice).
  3. Відсутня валідація даних: перед використанням даних з запитів застосуй валідацію через анотацію @Valid.
  4. Циклічні залежності: використовуй DI коректно, уникаючи взаємних посилань між компонентами.

Тепер твої контролери і сервіси готові обробляти запити, а мозок і вуха твого застосунку чудово відпрацювали. У наступній лекції ми продовжимо будівництво нашого full-stack і зануримося в питання безпеки та аутентифікації через Spring Security!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ