← Назад к вопросам

Должны ли у контроллера быть методы для взаимодействия с ним

2.0 Middle🔥 171 комментариев
#Spring Boot и Spring Data

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Должны ли у контроллера быть методы для взаимодействия с ним

Краткий ответ

Да, контроллер должен иметь методы (handler methods), которые обрабатывают HTTP запросы. Это основная его ответственность. Однако они должны быть тонкими и делегировать бизнес-логику сервисам.

Структура контроллера

Контроллер — это часть presentation layer, которая:

  1. Получает HTTP запросы
  2. Парсит и валидирует данные
  3. Вызывает сервис (application/business layer)
  4. Возвращает HTTP ответ
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    private final UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    // Handler method
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        UserDTO dto = new UserDTO(user);
        return ResponseEntity.ok(dto);
    }
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody CreateUserRequest request) {
        User user = userService.create(request.getName(), request.getEmail());
        UserDTO dto = new UserDTO(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }
}

Принцип тонкого контроллера

❌ Плохо — толстый контроллер

@RestController
public class UserController {
    
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        // Бизнес-логика в контроллере!
        if (user.getEmail() == null) {
            throw new IllegalArgumentException("Email required");
        }
        
        // Проверка уникальности email
        List<User> users = database.findAll();
        for (User u : users) {
            if (u.getEmail().equals(user.getEmail())) {
                throw new DuplicateEmailException();
            }
        }
        
        // Хеширование пароля
        String hashedPassword = bcrypt(user.getPassword());
        user.setPassword(hashedPassword);
        
        // Сохранение в БД
        database.save(user);
        
        return user;
    }
}

✅ Хорошо — тонкий контроллер

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    private final UserService userService;
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
        // Вся бизнес-логика в сервисе
        User user = userService.createUser(
            request.getName(),
            request.getEmail(),
            request.getPassword()
        );
        
        UserDTO dto = UserMapper.toDTO(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }
}

Ответственность контроллера

1. Получение параметров HTTP запроса

@GetMapping("/{id}")
public ResponseEntity<User> getUser(
    @PathVariable Long id,
    @RequestParam(required = false) String filter
) {
    // ...
}

2. Валидация входных данных

@PostMapping
public ResponseEntity<User> create(
    @Valid @RequestBody CreateUserRequest request // Spring валидирует
) {
    // ...
}

3. Обработка исключений

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException e) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND)
        .body(new ErrorResponse("User not found"));
}

4. Форматирование ответа

@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    UserDTO dto = new UserDTO(user); // Преобразование в DTO
    return ResponseEntity.ok(dto);
}

Методы контроллера для разных HTTP операций

@RestController
@RequestMapping("/api/v1/posts")
public class PostController {
    
    private final PostService postService;
    
    // GET — получение списка
    @GetMapping
    public ResponseEntity<List<PostDTO>> getAllPosts() {
        List<Post> posts = postService.findAll();
        return ResponseEntity.ok(PostMapper.toDTOList(posts));
    }
    
    // GET — получение по ID
    @GetMapping("/{id}")
    public ResponseEntity<PostDTO> getPost(@PathVariable Long id) {
        Post post = postService.findById(id);
        return ResponseEntity.ok(PostMapper.toDTO(post));
    }
    
    // POST — создание
    @PostMapping
    public ResponseEntity<PostDTO> createPost(@Valid @RequestBody CreatePostRequest request) {
        Post post = postService.create(request.getTitle(), request.getContent());
        return ResponseEntity.status(HttpStatus.CREATED).body(PostMapper.toDTO(post));
    }
    
    // PUT — полное обновление
    @PutMapping("/{id}")
    public ResponseEntity<PostDTO> updatePost(
        @PathVariable Long id,
        @Valid @RequestBody UpdatePostRequest request
    ) {
        Post post = postService.update(id, request.getTitle(), request.getContent());
        return ResponseEntity.ok(PostMapper.toDTO(post));
    }
    
    // PATCH — частичное обновление
    @PatchMapping("/{id}")
    public ResponseEntity<PostDTO> partialUpdate(
        @PathVariable Long id,
        @RequestBody Map<String, Object> updates
    ) {
        Post post = postService.partialUpdate(id, updates);
        return ResponseEntity.ok(PostMapper.toDTO(post));
    }
    
    // DELETE — удаление
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        postService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

Архитектурное разделение

Presentation Layer (Controller)
    ↓
Application Layer (Service)
    ↓
Domain Layer (Business Logic)
    ↓
Infrastructure Layer (Repository)

Контроллер НЕ должен

  • Обращаться напрямую к БД (только через сервис)
  • Содержать бизнес-логику
  • Работать с исключениями БД
  • Управлять транзакциями

Контроллер ДОЛЖЕН

  • Получать HTTP запросы
  • Валидировать входные данные
  • Вызывать сервис
  • Обрабатывать исключения приложения
  • Возвращать правильные HTTP статусы

Тестирование контроллера

@WebMvcTest(UserController.class)
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    public void testGetUser() throws Exception {
        User user = new User(1L, "John");
        when(userService.findById(1L)).thenReturn(user);
        
        mockMvc.perform(get("/api/v1/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}

Заключение

Да, контроллер должен иметь методы для взаимодействия с HTTP запросами. Это его основная функция. Однако методы контроллера должны быть тонкими — они только получают данные, валидируют и передают их в сервис, а затем возвращают результат. Вся бизнес-логика должна находиться в слое сервисов.