Каковы различия между слоями Controller, Service и Repository в Spring?

Ответ

Это стандартные слои архитектуры Spring-приложения, обеспечивающие разделение ответственности (SoC).

1. Repository (Слой доступа к данным)

  • Ответственность: Абстракция для работы с источником данных (БД, внешний API). Выполняет операции CRUD (Create, Read, Update, Delete).
  • Аннотация: @Repository (специализированный @Component, обрабатывает исключения доступа к данным).
  • Пример:
    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        Optional<User> findByEmail(String email);
    }

2. Service (Слой бизнес-логики)

  • Ответственность: Инкапсуляция сложной бизнес-логики, правил валидации, транзакций. Координирует работу нескольких репозиториев.
  • Аннотация: @Service (специализированный @Component).
  • Пример:

    @Service
    @Transactional
    public class UserService {
        @Autowired
        private UserRepository userRepository;
    
        public User registerUser(UserRegistrationDto dto) {
            // Бизнес-правило: email должен быть уникальным
            if (userRepository.findByEmail(dto.getEmail()).isPresent()) {
                throw new EmailAlreadyExistsException();
            }
            User user = new User(dto.getEmail(), dto.getPassword());
            return userRepository.save(user);
        }
    }

3. Controller (Презентационный слой)

  • Ответственность: Обработка HTTP-запросов, маппинг URL, валидация входных данных (DTO), возврат HTTP-ответов (JSON, HTML).
  • Аннотация: @RestController (для REST API) или @Controller (для MVC с представлениями).
  • Пример:

    @RestController
    @RequestMapping("/api/users")
    public class UserController {
        @Autowired
        private UserService userService;
    
        @PostMapping
        public ResponseEntity<User> createUser(@Valid @RequestBody UserRegistrationDto dto) {
            User created = userService.registerUser(dto);
            return ResponseEntity.created(URI.create("/api/users/" + created.getId())).body(created);
        }
    }

Поток данных: ControllerServiceRepository. Такое разделение упрощает тестирование, поддержку и модификацию кода.

Ответ 18+ 🔞

А, ну это же классика, блядь! Сейчас объясню на пальцах, как эти слои в Spring'е друг на друга накладываются, чтобы не получилась каша, в которой потом сам чёрт ногу сломит.

Смотри, вся эта архитектура — она как раз про то, чтобы не превратить проект в один большой файл, где всё намешано, как говно в проруби. Разделение ответственности, ёпта! Каждый пусть занимается своим делом, а не пытается быть мастером на все руки и в итоге нихуя не делает нормально.

1. Repository (Слой, который лезет в базу)

  • Чем занимается: Его задача — общаться с данными. С базой, с каким-нибудь внешним API, с файлом под кроватью. CRUD — его хлеб. Создать, прочитать, обновить, удалить. Больше от него нихуя не нужно.
  • Как помечается: @Repository. Это типа особый @Component, который ещё и исключения, связанные с данными, ловит и переваривает.
  • Пример, чтобы стало совсем ясно:
    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        Optional<User> findByEmail(String email); // Найди юзера по мылу, а нет — так иди нахуй (верни Optional.empty())
    }

    Вот и вся его работа. Не думает о бизнес-правилах, не валидирует нихуя — просто дай ему сущность, он её сохранит или достанет.

2. Service (Слой, где живёт вся логика и мозги)

  • Чем занимается: А вот тут уже начинается магия, блядь! Вся твоя бизнес-логика, правила, проверки, координация действий. Если нужно взять данные из одного репозитория, что-то с ними сделать и запихнуть в другой — это сюда. Сервис — это как прораб на стройке: репозитории — это разнорабочие с тачками, а сервис говорит им, куда бежать и что таскать.
  • Как помечается: @Service. Тоже разновидность бина, просто для понятности.
  • Смотри, как он может выглядеть:

    @Service
    @Transactional // Чтоб всё в одной транзакции было, а то полдела сделает и свалит
    public class UserService {
        @Autowired
        private UserRepository userRepository; // Взял в подчинение репозиторий
    
        public User registerUser(UserRegistrationDto dto) {
            // А вот бизнес-правило, ёбана! Мыло должно быть уникальным.
            if (userRepository.findByEmail(dto.getEmail()).isPresent()) {
                throw new EmailAlreadyExistsException(); // Если уже есть — пошёл нахуй с таким регистрированием!
            }
            // Всё ок, создаём юзера
            User user = new User(dto.getEmail(), dto.getPassword());
            // И говорим репозиторию: "Запиши этого товарища в базу, живо!"
            return userRepository.save(user);
        }
    }

    Видишь? Репозиторий тупо ищет, сервис думает, можно ли регистрировать.

3. Controller (Слой, который общается с внешним миром)

  • Чем занимается: Это лицо твоего приложения, его рот и уши. Прилетел HTTP-запрос — контроллер его поймал. Нужно принять JSON, проверить (валидация DTO), позвать нужный сервис, чтобы тот всё посчитал, и отдать ответ обратно — JSON, HTML, что тебе надо.
  • Как помечается: @RestController (для REST API, чтоб сам в JSON всё конвертил) или @Controller (если со старыми-добрыми JSP/Thymeleaf возиться).
  • И вот он в деле:

    @RestController
    @RequestMapping("/api/users") // Все запросы на /api/users будут сюда стекаться
    public class UserController {
        @Autowired
        private UserService userService; // Держит под рукой сервис
    
        @PostMapping // Ловит POST-запрос
        public ResponseEntity<User> createUser(@Valid @RequestBody UserRegistrationDto dto) {
            // @Valid — говорит Spring'у: "Проверь эту DTO на все аннотации валидации, не хуй собачий!"
            // Передаёт DTO в сервис, тот там всю логику проверяет и создаёт
            User created = userService.registerUser(dto);
            // Возвращает ответ с кодом 201 Created и ссылкой на созданный ресурс
            return ResponseEntity.created(URI.create("/api/users/" + created.getId())).body(created);
        }
    }

Итоговый поток, блядь, запомни: Запрос прилетает → его ловит Controller → он зовёт Service и говорит "сделай что-нибудь" → Service орет на Repository: "Принеси-ка мне данные оттуда!" → Repository лезет в базу → отдаёт данные наверх по цепочке → Controller пакует результат и шлет ответ клиенту.

Вот и вся магия. Каждый на своём месте, ничего лишнего не делает. А то начнёшь в репозитории бизнес-правила пихать, в контроллере — SQL-запросы писать, и получится пиздец, а не приложение. Тестировать невозможно, поддерживать — волосы дыбом. Так что держи слои в порядке, ёпта!