[플젝 정리] RESTful 설계, 예외처리 설계

최민수·2023년 10월 23일
0

프로젝트 정리

목록 보기
3/8

개인 블로그 프로젝트 개발을 하는 과정에서 RESTful API 설계예외처리 구조를 어떻게 가져가야 할까 고민을 많이 했었다.

다음은 프로젝트 진행 당시 공부했던 내용과 직접 사용한 과정이다.


RESTful 설계

REST API 가 무엇?

Representational State Transfer

HTTP 메서드(GET, POST, PUT, DELETE) 와 URI 를 기반으로, 각 요청이 어떤 동작을 하는지 요청 자체만으로 나타낼 수 있는 api 설계 방법이다.

HTTP 웹 설계의 장점을 최대한 활용할 수 있는 아키텍처 로써 REST 설계가 소개되었는데, REST API는 다음의 구성으로 이루어져 있다.

  • 자원 - URI
  • 행위 - HTTP 메서드
  • 표현

즉, REST API 설계 시 가장 중요한 것은 아래 2가지 원칙이다.

첫번째, URI는 정보의 자원을 표현하는 데에 집중.
두번째, 자원에 대한 행위는 HTTP 메서드(GET, POST, PUT, DELETE...) 를 통해 하는 데에 집중.

REST API 로 개발을 하는 이유?

협업 과정에서 HTTP 요청을 할 때
어떤 URL에 어떤 메서드를 사용할지에 대해 개발자들 간의 약속을 해놓으면, 이후에 별도의 상의 없이 요청에 대해 이해하기 쉽고 활용도 높게 이용할 수 있기 때문이라고 생각한다.

REST 의 특징

  1. Stateless 무상태성
  • 즉, 작업을 위한 상태 정보를 따로 보관 X.
  • 따라서, API 서버는 들어오는 요청만 처리하면 됨.
  • 서비스 자유도가 높아지고 불필요한 정보를 서버에서 관리하지 않아 단순한 설계가 가능.
  1. 자체 표현 구조
  • REST API 메세지만 보고도 쉽게 이해할 수 있는 자체 표현 구조로 되어 있음.
  1. Client-Server 구조
  • REST 서버: API 제공.
  • 클라이언트: 사용자 인증, 로그인 정보 관리
  • 이렇게 각각의 역할이 확실히 구분되기 때문에 서버와 클라이언트에서 개발해야 할 내용이 명확, 의존성이 줄어든다.

REST API 중심 규칙

  • URI는 정보의 자원을 표현! (동사보다는 명사로)
    GET /members/delete/1 -> DELETE /members/1

조회나 삭제 같은 동사의 개념은 HTTP 메서드가 표시해주니 걱정하지 않아도 된다.

만약 추가 정보를 전달하고 싶다면, query parameter를 이용하면 된다.
예를 들어, 영화 목록을 조회하는 GET /movies 뒤에 ?release_date=2023 과 같은 쿼리 파라미터를 전달해서 확장성 있는 요청을 수행할 수 있다.

REST API 의 단점?

REST API 의 장점은 위와 같이 많은데, 단점도 있을까?

REST API가 HTTP 메서드 기반의 기술이라는 점에서 단점을 생각해볼 수 있다.
HTTP 메서드를 활용하는 REST API 입장에서는 메서드의 일단 정해진 행동 패턴을 커스텀하기 어렵고, 정해진 대로 따라야 한다는 한계가 존재한다.

예를 들어, 세부적인 조회나 수정 과정을 입맛에 맞게 커스텀하고 싶은데 GET 이나 PUT 메서드 자체의 행동을 수정하기 불가능하다는 것이다.

또한, 어떤 리소스에 대해 어떤 api를 적용하는지에 대해서 문서화나 약속이 잘 진행되지 않으면 활용에 큰 문제가 생긴다는 단점도 있다.
이것은 REST api로 개발하기 위한 중요한 조건이기 때문에 충족되지 않으면 단점이 되는 것이기도 하다.


예외처리 설계

에러 메세지는 나가는 포맷이 일정해야 한다고 생각했다.

따라서 @ExceptionHandler@ControllerAdvice 를 사용해서 예외 처리 구조를 설계한다면 깔끔한 설계가 가능할 것이다.

또한, 최상위 Exception 클래스를 추상 클래스로 설계해 프로젝트에서 생성하는 예외처리 클래스들이 상속하게 만든다면 @ControllerAdvice 클래스에서 깔끔한 설정이 가능할 것이다.

프로젝트의 예외 처리 클래스들을 모두 ProjectChoiException 추상 클래스를 상속받은 클래스로 설계했다.

  • 인증/검증 관련 예외 처리 클래스: UnauthorizedUser, DuplicateEmailException, InvalidSignIn, InvalidRequest
  • 존재하지 않는 글에 대한 처리: PostNotFound

다만, 어떤 예외냐에 따라 상태 코드(status)오류 메세지(msg) 는 다르게 가져가야 한다.

이는 추상 메서드로 정의해두고 추상 클래스를 구현할 때 메서드 재정의생성자 파라미터로 넘기는 과정을 통해 해결하기로 했다.

아래 코드는 최상위 추상 클래스인 ProjectChoiException 클래스이다.

@Getter
public abstract class ProjectChoiException extends RuntimeException {

    public final Map<String, String> validation = new HashMap<>();

    public ProjectChoiException(String message) {
        super(message);
    }

    public ProjectChoiException(String message, Throwable cause) {
        super(message, cause);

    }

    public abstract int getStatusCode();

    public void addValidation(String fieldName, String message) {
        validation.put(fieldName, message);
    }
}

그리고 이를 상속한 클래스를 생성한다.
만약 글 조회 실패 (없는 데이터 조회: 404) 예외 처리 클래스의 경우 아래와 같다.

/**
 * StatusCode = 404
 */
public class PostNotFound extends ProjectChoiException {

    private static final String MESSAGE = "존재하지 않는 글입니다.";

    public PostNotFound() {
        super(MESSAGE);
    }

    @Override
    public int getStatusCode() {
        return 404;
    }
}

이렇게 설계된 예외처리 구조는 ExceptionController 라는 새로운 클래스를 하나 만들어서,
아래와 같이 @ControllerAdvice 어노테이션을 통해 설정해주면 끝이다.

@RestControllerAdvice
public class ExceptionController {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResponse invalidRequestHandler(MethodArgumentNotValidException e) {
        ErrorResponse response = ErrorResponse.builder()
                .code("400")
                .message("Bad Request!")
                .build();

        for (FieldError fieldError : e.getFieldErrors()) {
            response.addValidation(fieldError.getField(), fieldError.getDefaultMessage());
        }
        return response;
    }

    @ExceptionHandler(ProjectChoiException.class)
    public ResponseEntity<ErrorResponse> projectChoiExceptionHandler(ProjectChoiException e) {
        int statusCode = e.getStatusCode();

        ErrorResponse response = ErrorResponse.builder()
                .code(String.valueOf(statusCode))
                .message(e.getMessage())
                .validation(e.getValidation())
                .build();

        return ResponseEntity.status(statusCode)
                .body(response);
    }
}

아래 projectChoiExceptionHandler 메서드가 방금 전 커스텀한 예외처리 상황 모두를 잡는 로직이고,

위의 invalidRequestHandler 메서드는 커스텀 예외 처리를 제외한 다른 예외처리를 처리하기 위해 따로 설정해준 로직이다.

profile
CS, 개발 공부기록 🌱

0개의 댓글