[Spring Boot] Controller와 Service 제대로 구분하기

Juice-Han·2024년 10월 23일
0

Spring Boot

목록 보기
1/3
post-thumbnail

스프링 부트의 Controller와 Service를 제대로 구분하지 못했던 과거의 나를 반성하고
앞으로는 코드를 제대로 작성하고자 내용을 정리해보려고 한다.

스프링 계층(layer)

스프링 계층

스프링에는 계층이 존재한다.

Client가 전송한 요청을 Controller, Service, Repository 순으로 처리하고
다시 역순으로 데이터를 반환하며 응답을 전송한다.
이렇게 여러개의 계층을 나눈 까닭은 MVC 패턴에 맞춰 개발하기 위함이다.

이를 통해 협업의 편리, 유지보수 용이, 코드간 의존성 낮춤, 코드 중복 제거 등
여러 장점이 생긴다.

간단하게 Controller와 Service에 대해 알아보자.

Controller란

Controller는 클라이언트의 요청에 맞게 함수를 실행해주고, 응답을 전송해주는 역할을 한다.
데이터베이스에 직접 접근하지 않고, 실행한 함수의 리턴 결과로 오는 데이터만 가지고 응답을 전송한다.

Service란

Service는 말 그대로 서비스의 메인 로직을 수행하는 역할을 한다.
Controller가 Service의 함수를 실행하면 Service는 Repository를 통해 데이터베이스에 접근하여
필요한 정보를 가져오고 가공해서 Controller에게 다시 전달한다.

구분을 제대로 해야하는 이유?

제대로 구분하지 않는다면 처음에 말했던 MVC 패턴을 따랐을 때 생기는 장점을 모두 잃게 된다.

Controller는 Controller의 역할에 맞게 코드를 작성하고
Service는 Service의 역할에 맞게 코드를 작성해야 하는 것이다.

계층에 맞게 코드를 작성하여 효율적으로 코딩하는 게 중요

내가 이전에 작성했던 코드를 보며 뭐가 잘못됐는지 확인해보자.

내가 작성했던 잘못된 코드

@PostMapping("/user/signUp")
public ResponseEntity<MessageDTO> signUp(@Valid @RequestBody UserSignUpRequestDTO request, BindingResult bindingResult) {
    MessageDTO messageDTO = new MessageDTO();

    if (bindingResult.hasErrors()) { // 입력받은 request body 값에 정상적인 값들이 들어있는지 확인
        List<FieldError> list = bindingResult.getFieldErrors();
        for (FieldError error : list) {
            messageDTO.setMessage(error.getDefaultMessage());
            return new ResponseEntity<>(messageDTO, HttpStatus.BAD_REQUEST);
        }
    }

    boolean isDuplicated = userService.checkEmailDuplication(request.getEmail()); // 아이디 중복 체크

    if (isDuplicated) {
        messageDTO.setMessage("중복된 아이디입니다.");
        return new ResponseEntity<>(messageDTO, HttpStatus.BAD_REQUEST);
    }

    try {
        userService.signUp(request); // 중복되지 않았으면 계정 생성
        messageDTO.setMessage("회원가입 성공");
        return new ResponseEntity<>(messageDTO, HttpStatus.CREATED);
    }catch(IllegalArgumentException e){
        messageDTO.setMessage("학교 정보가 잘못되었습니다.");
        return new ResponseEntity<>(messageDTO, HttpStatus.BAD_REQUEST);
    }
}

유저 회원가입 요청을 처리하는 Controller 함수이다.

잘못된 점은 크게 두 가지로 보인다.

  1. Service에서 boolean 값을 받아옴
  2. Controller에서 Exception 처리

첫 번째, 계층 간 데이터는 DTO를 통해 전달되어야 한다.
그렇지 않고 바로 데이터를 전달한다면 계층 간 의존성이 높아지기 때문에
유지보수가 힘들어진다.

두 번째, Controller에서는 Controller 계층의 역할에 맞게 코드를 작성해야 한다.
Controller는 Client의 요청에 맞는 함수를 매핑하고 응답을 반환하는 계층이다.
여기서 오류처리를 해버린다면 코드 복잡도가 증가하고 반복되는 코드의 양이 늘어나 유지보수가 어려워진다.

오류처리는 Service 계층에서 진행하고 ExceptionHandler를 사용해서 응답처리를 하자.

다음은 최근에 작성한 올바른 코드이다.

최근에 작성한 코드

@PostMapping("/api/articles")
public ResponseEntity<AddArticleResponse> saveArticle(@RequestBody AddArticleRequest addArticleRequest) {
    AddArticleResponse addArticleResponse = boardService.saveArticle(addArticleRequest);
    return new ResponseEntity<>(addArticleResponse, HttpStatus.CREATED);
}

@GetMapping("/api/articles")
public ResponseEntity<GetAllArticlesResponse> findAllArticles(){
    GetAllArticlesResponse getAllArticlesResponse = boardService.findAllArticles();
    return new ResponseEntity<>(getAllArticlesResponse, HttpStatus.OK);
}

@GetMapping("/api/articles/{id}")
public ResponseEntity<GetArticleResponse> findArticleById(@PathVariable("id") Integer id){
    GetArticleResponse articleDTO = boardService.findArticleById(id);
    return new ResponseEntity<>(articleDTO, HttpStatus.OK);
}

게시글과 관련된 요청을 처리하는 Controller다.
이전 코드와는 비교가 안 될 정도로 코드가 깔끔하다.

Service 계층의 함수를 실행하여 결과를 DTO로 받아오고 다른 서비스 로직없이 응답을 반환했기 때문에 코드가 간결해진 것이다.

이렇게 코드를 작성하면 Client 요청이 왔을 때 어떤 Service 함수가 실행되고, 어떤 값이 응답에 담겨 전달되는 지 알아보기 쉽다.
또한, Controller와 Service가 제대로 분리되어 유지보수가 쉬워진다.

앞으로 코드를 작성할 땐 Controller와 Service 구분을 제대로 해서 코드를 작성하자!

출처

profile
배우고 기록하고

0개의 댓글