Service: “мозок” застосунку
Service-шар — це місце, де живе логіка: перевірки, правила, обробка помилок, транзакції. Controller приймає HTTP, Repository працює з БД, а Service зв’язує все в нормальну систему.
UserService (13.1.png)
Рис. 13.1. Клас UserService з анотацією @Service, який використовує UserRepository для доступу до таблиці users.

1) Навіщо взагалі потрібен Service (а не “все в контролері”)

На малих прикладах здається, що можна робити все в контролері: прийняв запит → викликав репозиторій → повернув відповідь. Але в реальному проєкті так швидко починається хаос.

  • Controller має бути тонким: HTTP-вхід/вихід (Request/Response).
  • Repository має бути простим: CRUD і доступ до даних.
  • Service має бути розумним: правила, перевірки, логіка.

Правило для студентів: якщо ти відчуваєш, що в контролері з’являються перевірки, if-и, додаткова логіка — значить це треба винести в Service.

2) Схема шарів (як це працює в Spring Boot проєкті)

HTTP запит → Controller → Service → Repository → Database ↑ ↓ HTTP відповідь ← DTO/Entity

Ідея: кожен шар робить своє. Якщо потім ти додаси нові правила (наприклад “email має бути унікальним”) — ти змінюєш Service, а не переписуєш весь проєкт.

3) Розбір @Service: що це за анотація і що вона дає

@Service — це “мітка”, що клас є сервісом (Spring Bean).

  • Spring автоматично знаходить цей клас під час сканування пакетів.
  • Створює об’єкт (bean) і керує його життєвим циклом.
  • Дозволяє іншим класам “вколоти” (inject) цей сервіс.

Технічно @Service — це спеціалізація @Component. Але @Service читається людиною: “це бізнес-логіка”.

4) Dependency Injection: чому ми передаємо repository в конструктор

Ось ключова частина:

private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
  • Spring сам створює об’єкт UserRepository (проксі).
  • Потім, коли створює UserService, передає репозиторій у конструктор.
  • Ти не пишеш new UserRepository() — це робить контейнер Spring.

Чому саме конструктор: це найбільш “чистий” і надійний варіант. Поле стає обов’язковим, сервіс не може існувати без репозиторію.

5) Чому поле зроблено final

  • final означає: після ініціалізації поле не можна переприсвоїти.
  • це зменшує кількість помилок (хтось випадково не затре репозиторій).
  • це хороший стиль для DI: залежності мають бути незмінні.

У нормальному проєкті “залежності” сервісу — це константа: сьогодні userRepository, завтра він не має стати іншим.

6) Розбір методів сервісу (що робить кожен)

getAllUsers()

public List<User> getAllUsers() { return userRepository.findAll(); }
  • повертає список всіх користувачів з таблиці users
  • під капотом findAll() → SQL типу SELECT * FROM users
  • це “читання” (read) з БД

Тут логіки поки мало — але це правильне місце, щоб додати її потім (фільтри, сортування, пагінацію).

createUser(User user)

public User createUser(User user) { return userRepository.save(user); }
  • зберігає користувача в БД
  • save() зробить INSERT, якщо id немає
  • після збереження об’єкт може повернутись вже з заповненим id

Саме тут найчастіше з’являються перевірки: валідність email, унікальність, мінімальна довжина імені.

7) Де саме має бути “реальна логіка” (приклади для пояснення студентам)

Зараз сервіс просто викликає репозиторій. Але в реальності тут буде, наприклад:

  • перевірка, що email не порожній і має нормальний формат;
  • перевірка, що такого email ще немає: userRepository.findByEmail();
  • нормалізація: trim(), toLowerCase() для email;
  • обробка помилок і повернення зрозумілої відповіді клієнту;
  • логування: хто створив користувача, коли, з яким email;
  • транзакції: якщо треба виконати кілька дій як один “атомарний” крок.

Висновок: Service — це місце, де система стає “розумною”. Repository лише зчитує/пише, а Service вирішує “можна чи не можна”.

8) Анотація @Transactional (важливо, але можна підключати пізніше)

Часто сервісні методи позначають @Transactional. Це означає: виконати всі дії всередині методу як одну транзакцію.

  • якщо всередині методу кілька операцій — вони або всі успішні, або всі відкотяться;
  • це критично для грошей, статусів, складних сценаріїв.

На цій практичній можна не заглиблюватися, але важливо, щоб студенти знали: транзакції — це відповідальність Service-шару.

9) Типові помилки (попередження)

  • Controller напряму викликає Repository → логіка роз’їжджається по всьому проєкту.
  • Service починає будувати HTTP-відповідь (ResponseEntity) → це вже зона Controller.
  • Валідації немає ніде → в БД з’являються “сміттєві” дані.

Підсумок: ми створили UserService як правильний шар між Controller і Repository. Поки він простий, але саме тут потім з’являться перевірки, правила і “реальна логіка”.