본문 바로가기
개발자 준비/개발 공부

API 사용성 개선: 사용자 정의 예외를 이용한 API 응답 세분화 및 상세화

by osul_world 2022. 12. 2.
728x90

사용자 정의 예외를 통해 API 응답 세분화를 적용한 이유

API 원작자 없이도 프론트엔드 같은 API 이용자가 API를 쉽고 정확하게 사용하도록 하기 위해선,

API 명세서 뿐만 아니라 상태코드와 메세지를 통한 상세하고 정교한 응답이 필수적이라고 생각합니다.

 

이 과정에서, 특정 요청을 처리함에 있어 잘못 되었다면 어떻게 잘못 되었는지 어떤 처리를 해줘야 하는지 알려줘야 합니다.

또한, 정교한 예외 처리로 서버의 리소스가 훼손되는 문제도 방지해야 합니다.

 

이를 위해, 발생할 수 있는 예외별로 사용자 정의 예외를 생성하고 상태코드와 에러메세지를 통해 상세한 예외 상황을 응답할 수 있도록 할 것입니다.

 

이 과정에서, Spring ExceptionResolver를 이용하여 예외 관리가 편리하게 코드를 개선한 과정을 정리하겠습니다.

 

Spring ExceptionResolver란?

웹 환경에서 예외가 발생하게 되면 기존의 예외 전파 및 핸들링 과정은 아래와 같습니다.

1. WAS <- 필터 <- 서블릿(디스패쳐 서블릿) <- 인터셉터 <- 컨트롤러(예외발생)

2. WAS 예외 페이지 정보 확인 -> WAS (`/error-page/500` 다시 요청) -> 예외 헨들링 컨트롤러 호출(/errorpage/500)

발생한 예외가 WAS까지 전파 되었다가 다시 회귀하여 돌아와 작성한 예외 컨트롤러를 통해 적절히 처리됩니다.

Spring ExceptionResolver를 이용하여 이런 비효율적인 처리 방식을 개선할 수 있습니다.

 

디스패쳐 서블릿으로 예외가 전달되면 ExceptionResolver에게 예외 처리가 가능한지 확인하여 처리된 결과를 WAS에 정상 응답하도록 할 수 있습니다.

 

이 응답은 HTML , JSON 모두 가능합니다.

또한, @ControllerAdvice 또는 @RestControllerAdvice 어노테이션을 이용하면 아래와 같이 예외 처리 로직을 핵심 로직에서 별도로 분리할 수 있습니다.

package scheduler.api.exception.exceptionController;

@Slf4j
@RestControllerAdvice
public class ExceptionController  {

    @ExceptionHandler(DuplicateDataException.class)
    public ResponseEntity<ExceptionResult> DpEx(DuplicateDataException e , HttpServletRequest request){
        ExceptionResult exceptionResult = new ExceptionResult(new Date(),
                "Duplicate-Data-Denied",
                e.getMessage(),
                request.getRequestURI());

        return new ResponseEntity<>(exceptionResult,e.getStatus());
    }

    @ExceptionHandler(ValidatedException.class)
    public ResponseEntity<ExceptionResult> VlEx(ValidatedException e , HttpServletRequest request) {
        ExceptionResult exceptionResult = new ExceptionResult(new Date(),
                "Wrong-Validated",
                e.getMessage(),
                request.getRequestURI());

        return new ResponseEntity<>(exceptionResult,e.getStatus());
    }
}

사용자 정의 예외로 세분화 하기

사용자 정의 예외 클래스로 중복된 데이터를 저장하는 요청시 발생하는 DuplicateDataException , 도메인 생성시 검증 문제가 발생시 발생하는 ValidatedException 등 을 생성합니다.

package scheduler.api.exception.exceptionDomain;

//checked 예외로 생성
public class DuplicateDataException extends Exception {

    private HttpStatus status;

    public DuplicateDataException(String message, HttpStatus status) {
        super(message);
        this.status = status;
    }

    public HttpStatus getStatus(){
        return status;
    }
}

로직 처리 과정에서 특정 케이스에 예외를 발생 시킨다면,

throw new DuplicateDataException("이미 구독한 공고 입니다.", HttpStatus.CONFLICT);

ExceptionHandler 에서 해당 예외를 잡아 아래와 같이 정상 응답을 만들어 WAS에 전달합니다.

@RestControllerAdvice
public class ExceptionController  {

    @ExceptionHandler(DuplicateDataException.class)
    public ResponseEntity<ExceptionResult> DpEx(DuplicateDataException e , HttpServletRequest request){
        ExceptionResult exceptionResult = new ExceptionResult(new Date(),
                "Duplicate-Data-Denied",
                e.getMessage(),
                request.getRequestURI());

        return new ResponseEntity<>(exceptionResult,e.getStatus());
    }

	...

}

[번외] 에러처리 보단 예방?

사용자가 잘못된 입력을 한 경우 ‘에러 처리를 통해 재 입력을 요구’하기 보단 에초에 ‘잘못된 입력을 할수 없도록 제한’함 으로써 에러를 예방하는게 바람직 합니다.

( ex. 숫자를 요구하는 입력엔 숫자만 입력 가능하도록 제한하는 기능을 추가한다.)

 

그러나, ‘예방’도 상황에 따라 적용해야 합니다.

 

예를들어, 은행 서비스에서 결제 버튼을 누르면 결제확인 메세지로 잘못된 결제를 예방할 수 있지만, 이러한 예방에도 잘못 결제하는 케이스가 많았고 결국 은행 서비스는 취소기능을 개발해야할 수 도 있습니다.

 

결국 두번의 일을 한 셈

이 경우엔, 결제 후 결제 목록으로 화면 전환을 시켜 사용자가 스스로 취소하도록 책임을 부여하는게 바람직

 

사용자를 불완전한 존재로 인식하지 말것

위 은행의 사례에서 ‘결제확인’ 과 ‘취소기능’을 둘다 만들면 안될까? 간단한 사례이니 만들어도 되겠지만, 이렇게 매번 사용자를 불완전한 존재로 인식하고 과하게 배려하게 되면 서비스의 복잡도와 불완전성이 증가할 것 입니다.

사용자 정의 예외를 통해 API 응답 세분화를 적용한 이유

API 원작자 없이도 프론트엔드 같은 API 이용자가 API를 쉽고 정확하게 사용하도록 하기 위해선,

API 명세서 뿐만 아니라 상태코드와 메세지를 통한 상세하고 정교한 응답이 필수적이라고 생각합니다.

 

이 과정에서, 특정 요청을 처리함에 있어 잘못 되었다면 어떻게 잘못 되었는지 어떤 처리를 해줘야 하는지 알려줘야 합니다.

또한, 정교한 예외 처리로 서버의 리소스가 훼손되는 문제도 방지해야 합니다.

 

이를 위해, 발생할 수 있는 예외별로 사용자 정의 예외를 생성하고 상태코드와 에러메세지를 통해 상세한 예외 상황을 응답할 수 있도록 할 것입니다.

 

이 과정에서, Spring ExceptionResolver를 이용하여 예외 관리가 편리하게 코드를 개선한 과정을 정리하겠습니다.

Spring ExceptionResolver란?

웹 환경에서 예외가 발생하게 되면 기존의 예외 전파 및 핸들링 과정은 아래와 같습니다.

1. WAS <- 필터 <- 서블릿(디스패쳐 서블릿) <- 인터셉터 <- 컨트롤러(예외발생)

2. WAS 예외 페이지 정보 확인 -> WAS (`/error-page/500` 다시 요청) -> 예외 헨들링 컨트롤러 호출(/errorpage/500)

발생한 예외가 WAS까지 전파 되었다가 다시 회귀하여 돌아와 작성한 예외 컨트롤러를 통해 적절히 처리됩니다.

Spring ExceptionResolver를 이용하여 이런 비효율적인 처리 방식을 개선할 수 있습니다.

 

디스패쳐 서블릿으로 예외가 전달되면 ExceptionResolver에게 예외 처리가 가능한지 확인하여 처리된 결과를 WAS에 정상 응답하도록 할 수 있습니다.

 

이 응답은 HTML , JSON 모두 가능합니다.

또한, @ControllerAdvice 또는 @RestControllerAdvice 어노테이션을 이용하면 아래와 같이 예외 처리 로직을 핵심 로직에서 별도로 분리할 수 있습니다.

package scheduler.api.exception.exceptionController;

@Slf4j
@RestControllerAdvice
public class ExceptionController  {

    @ExceptionHandler(DuplicateDataException.class)
    public ResponseEntity<ExceptionResult> DpEx(DuplicateDataException e , HttpServletRequest request){
        ExceptionResult exceptionResult = new ExceptionResult(new Date(),
                "Duplicate-Data-Denied",
                e.getMessage(),
                request.getRequestURI());

        return new ResponseEntity<>(exceptionResult,e.getStatus());
    }

    @ExceptionHandler(ValidatedException.class)
    public ResponseEntity<ExceptionResult> VlEx(ValidatedException e , HttpServletRequest request) {
        ExceptionResult exceptionResult = new ExceptionResult(new Date(),
                "Wrong-Validated",
                e.getMessage(),
                request.getRequestURI());

        return new ResponseEntity<>(exceptionResult,e.getStatus());
    }
}

사용자 정의 예외로 세분화 하기

사용자 정의 예외 클래스로 중복된 데이터를 저장하는 요청시 발생하는 DuplicateDataException , 도메인 생성시 검증 문제가 발생시 발생하는 ValidatedException 등 을 생성합니다.

package scheduler.api.exception.exceptionDomain;

//checked 예외로 생성
public class DuplicateDataException extends Exception {

    private HttpStatus status;

    public DuplicateDataException(String message, HttpStatus status) {
        super(message);
        this.status = status;
    }

    public HttpStatus getStatus(){
        return status;
    }
}

로직 처리 과정에서 특정 케이스에 예외를 발생 시킨다면,

throw new DuplicateDataException("이미 구독한 공고 입니다.", HttpStatus.CONFLICT);

ExceptionHandler 에서 해당 예외를 잡아 아래와 같이 정상 응답을 만들어 WAS에 전달합니다.

@RestControllerAdvice
public class ExceptionController  {

    @ExceptionHandler(DuplicateDataException.class)
    public ResponseEntity<ExceptionResult> DpEx(DuplicateDataException e , HttpServletRequest request){
        ExceptionResult exceptionResult = new ExceptionResult(new Date(),
                "Duplicate-Data-Denied",
                e.getMessage(),
                request.getRequestURI());

        return new ResponseEntity<>(exceptionResult,e.getStatus());
    }

	...

}

[번외] 에러처리 보단 예방?

사용자가 잘못된 입력을 한 경우 ‘에러 처리를 통해 재 입력을 요구’하기 보단 에초에 ‘잘못된 입력을 할수 없도록 제한’함 으로써 에러를 예방하는게 바람직 합니다.

( ex. 숫자를 요구하는 입력엔 숫자만 입력 가능하도록 제한하는 기능을 추가한다.)

그러나, ‘예방’도 상황에 따라 적용해야 합니다.

 

예를들어, 은행 서비스에서 결제 버튼을 누르면 결제확인 메세지로 잘못된 결제를 예방할 수 있지만, 이러한 예방에도 잘못 결제하는 케이스가 많았고 결국 은행 서비스는 취소기능을 개발해야할 수 도 있습니다.

 

결국 두번의 일을 한 셈

이 경우엔, 결제 후 결제 목록으로 화면 전환을 시켜 사용자가 스스로 취소하도록 책임을 부여하는게 바람직

 

사용자를 불완전한 존재로 인식하지 말것

위 은행의 사례에서 ‘결제확인’ 과 ‘취소기능’을 둘다 만들면 안될까? 간단한 사례이니 만들어도 되겠지만, 이렇게 매번 사용자를 불완전한 존재로 인식하고 과하게 배려하게 되면 서비스의 복잡도와 불완전성이 증가할 것 입니다.

 

728x90