Какие классы и интерфейсы использовать для контроллера, сервиса, репозитория и маппера в Spring REST API
Что проверяют
Понимаете ли вы, зачем приложение делится на слои и что каждая аннотация реально делает, а не просто помечает класс. Следом обычно спрашивают, почему сущность нельзя отдавать из контроллера напрямую и где ставить @Transactional.
Как отвечать
Пройдите по слоям:
- контроллер: класс с
@RestController(это@Controllerплюс@ResponseBody), маршруты через@GetMapping,@PostMappingи другие. Принимает и отдаёт DTO, валидирует вход через@Valid, возвращаетResponseEntity, когда нужен статус или заголовок. Ошибки собираются в одном месте через@RestControllerAdviceи@ExceptionHandler, для формата ответа подходитProblemDetail; - сервис: класс с
@Service, бизнес-логика и границы транзакций через@Transactional. Пара «интерфейс плюс реализация» нужна, если реализаций несколько или так принято в команде, иначе хватит класса; - репозиторий: интерфейс, наследующий
JpaRepository<Entity, Long>. Реализацию генерирует Spring Data, методы строятся по имени (findByEmail) или через@Query. Аннотация@Repositoryздесь не нужна: она имеет смысл на собственных классах доступа к данным, где включает трансляцию исключений вDataAccessException; - маппер: интерфейс MapStruct с
@Mapper(componentModel = "spring"), реализация генерируется при компиляции. Альтернатива это ручной маппинг или фабричные методы вrecord-DTO.
Зависимости внедряйте через конструктор с final-полями: при одном конструкторе @Autowired не нужен, а класс легко тестировать без контекста Spring.
Где спотыкаются
Отдают JPA-сущность прямо из контроллера. Ленивые связи при сериализации дают LazyInitializationException или N+1 запросов, а схема базы становится публичным контрактом API.
Кладут бизнес-логику в контроллер или, наоборот, превращают сервис в пустую прослойку над репозиторием.
Ставят @Transactional на приватный метод или вызывают транзакционный метод из того же класса. Прокси Spring такой вызов не перехватывает, и транзакция не открывается.