티스토리 뷰

결과 메시지 설계 

Error Message 구성 

field type

Description

httpStatus int

결과 코드, 정상인 경우 200

errorMessage String

에러 메시지

detailMessage

String

에러 상세 메시지 

HTTP 응답상태코드 (rfc2616 참고)

성공

200 : OK, 요청 정상 처리 
201 : Created, 생성 요청 성공
202 : Accepted, 비동기 요청 성공
204 : No Content, 요청 정상 처리, 응답 데이터 없음. 

실패

400 : Bad Request, 요청이 부적절 할 때, 유효성 검증 실패, 필수 값 누락 등. 
401 : Unauthorized, 인증 실패, 로그인하지 않은 사용자 또는 권한 없는 사용자 처리
402 : Payment Required
403 : Forbidden, 인증 성공 그러나 자원에 대한 권한 없음. 삭제, 수정시 권한 없음. 
404 : Not Found, 요청한 URI에 대한 리소스 없을 때 사용. 
405 : Method Not Allowed, 사용 불가능한 Method를 이용한 경우. 
406 : Not Acceptable, 요청된 리소스의 미디어 타입을 제공하지 못할 때 사용.
408 : Request Timeout
409 : Conflict, 리소스 상태에 위반되는 행위 시 사용.
413 : Payload Too Large
423 : Locked
428 : Precondition Required
429 : Too Many Requests
 
500 : 서버 에러 

 

* 응답 상태 코드 중 사용할 만한 것만 선별한 것입니다. 

 

참고 소스 (스프링 기반)

ErrorMessage.java

@Getter
@NoArgsConstructor
public class ErrorResponse {

    private int httpStatus;

    private String errorMessage;

    private String detailMessage;

    public ErrorResponse(int httpStatus, String errorMessage, String detailMessage) {
        super();
        this.httpStatus = httpStatus;
        this.errorMessage = errorMessage;
        this.detailMessage = detailMessage;
    }
    
}​

Test

    @GetMapping("/api/users")
    public ResponseEntity<List<User>> getUsers(User user) {

        List<User> users = new ArrayList<>();
        for(int i=0; i<10;i++) {
            users.add(new User("user_"+i, "사용자_"+i, "facebook"));
        }
        return ResponseEntity.ok(users);

    }

 

결과 

    • [
      1. {
        • "userId":"user_0",
        • "userPwd":null,
        • "name":"사용자_0",
        • "authType":"facebook"
        },
      2. {
        • "userId":"user_1",
        • "userPwd":null,
        • "name":"사용자_1",
        • "authType":"facebook"
        },
      3. {
        • "userId":"user_2",
        • "userPwd":null,
        • "name":"사용자_2",
        • "authType":"facebook"
        },
      4. {
        • "userId":"user_3",
        • "userPwd":null,
        • "name":"사용자_3",
        • "authType":"facebook"
        },
      5. {
        • "userId":"user_4",
        • "userPwd":null,
        • "name":"사용자_4",
        • "authType":"facebook"
        },
      6. {
        • "userId":"user_5",
        • "userPwd":null,
        • "name":"사용자_5",
        • "authType":"facebook"
        },
      7. {
        • "userId":"user_6",
        • "userPwd":null,
        • "name":"사용자_6",
        • "authType":"facebook"
        },
      8. {
        • "userId":"user_7",
        • "userPwd":null,
        • "name":"사용자_7",
        • "authType":"facebook"
        },
      9. {
        • "userId":"user_8",
        • "userPwd":null,
        • "name":"사용자_8",
        • "authType":"facebook"
        },
      10. {
        • "userId":"user_9",
        • "userPwd":null,
        • "name":"사용자_9",
        • "authType":"facebook"
        }
      ]

 

예외 생성(스프링 기반) 

에러를 어떻게 처리할지를 먼저 결정하고 그에 따라 Exception을 설계합니다. 

Exception 발생시 Respons status는 Exception에 정의한 HttpStatus를 활용할거며 에러 메시지와 HttpStatus의 value를 반환할 것입니다. 

AbstractEbloBaseException.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.springframework.http.HttpStatus;
 
public abstract class AbstractEbloBaseException extends RuntimeException {
 
    private static final long serialVersionUID = 1L;
 
    public AbstractEbloBaseException() {
        super();
    }
 
    public AbstractEbloBaseException(String msg) {
        super(msg);
    }
 
    public AbstractEbloBaseException(Throwable e) {
        super(e);
    }
 
    public AbstractEbloBaseException(String errorMessge, Throwable e) {
        super(errorMessge, e);
    }
 
    public abstract HttpStatus getHttpStatus();
 
}
 
cs
 

EbloNotFoundException.java 

not found exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.springframework.http.HttpStatus;
 
public class EbloNotFoundException extends AbstractEbloBaseException {
 
    private static final long serialVersionUID = 1L;
 
    public EbloNotFoundException() {
        super();
    }
 
    public EbloNotFoundException(Throwable e) {
        super(e);
    }
 
    public EbloNotFoundException(String errorMessge) {
        super(errorMessge);
    }
 
    public EbloNotFoundException(String errorMessge, Throwable e) {
        super(errorMessge, e);
    }
 
    public HttpStatus getHttpStatus() {
        return HttpStatus.NOT_FOUND;
    }
}
cs

 

EbloInvalidRequestException.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.springframework.http.HttpStatus;
 
public  class EbloInvalidRequestException extends AbstractEbloBaseException {
 
    private static final long serialVersionUID = 1L;
 
    public EbloInvalidRequestException() {
        super();
    }
 
    public EbloInvalidRequestException(Throwable e) {
        super(e);
    }
 
    public EbloInvalidRequestException(String errorMessge) {
        super(errorMessge);
    }
 
    public EbloInvalidRequestException(String errorMessge, Throwable e) {
        super(errorMessge, e);
    }
 
    public HttpStatus getHttpStatus() {
        return HttpStatus.BAD_REQUEST;
    }
}
cs

 

이외 EbloForbiddenException, EbloUnauthorizedException, EbloUnknownException, EbloSystemException 등 공통 Exception 생성. 

에러 코드는 스프링의 HttpStatus 이용합니다. 

 

Exception 처리 

스프링에서는 Controller based Exception Handling과 Global Exception Handling 두가지 형태로 처리 할 수 있습니다. 

 

참고 

Exception Handling in Spring MVC : https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

 

저는 서비스에 따라 각각 처리할 때 공통 컨트롤러를 만들고 그것을 상속 받아서 처리하게 만들었습니다.

AbstractBaseRestController. java

@RestControllerAdvice(basePackages = "eblo.example.common.api")
public class APIExceptionControllerAdvice {

    @ExceptionHandler(AbstractEbloBaseException.class)
    protected ResponseEntity<ErrorResponse> handleApiException(AbstractEbloBaseException e) {
        return ResponseEntity
                .status(e.getHttpStatus())
                .body(
                    new ErrorResponse(
                            e.getHttpStatus().value(),
                            e.getHttpStatus().getReasonPhrase(),
                            e.getDetailMessage()
                    )
                );
    }

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> exception(Exception e) {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(
                        new ErrorResponse(
                                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                                HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
                                e.getLocalizedMessage()
                        )
                );
    }
}

Rest 서비스에 대한 컨트롤러입니다. 

각 서비스, 컨트롤러에서 예외를 던지면 @ExceptionHandler를 통해 예외를 자동 처리 해주게 됩니다. 

예외에 따라 Response status를 결정하고 에러 코드와 메시지를 생성합니다. 

이렇게 예외를 처리하게 되면 불필요한 코드를 다 제거 할 수 있게 되고 소스의 가독성 역시 엄청나게 향샹 됩니다. 

 

* 이외 일반 컨트롤러에서 에러 발생시 에러 페이지로 연결하는 공통 컨트롤러를 만들어서 사용하게 되면 코딩이 한결 수월해 집니다. 

 

컨트롤러 예제 

UserAuthRestController.java

@RestController
@Slf4j
public class UserAuthRestController { 

    @GetMapping("/api/users")
    public ResponseEntity<List<User>>  getUsers(User user) {
        Assert.notNull(user.getUserId(), "요청 사용자 id는 필수 입니다.");
        List<User> users = new ArrayList<>();
        for(int i=0; i<10;i++) {
            users.add(new User("user_"+i, "사용자_"+i, "facebook"));
        }

        return ResponseEntity.ok(users);
    }
}

마무리

REST 서비스에서 사용하는 공통 MESSAGE와 EXCEPTION에 대한 예제를 만들어 보았습니다. 

이중에 Exception은 매우 중요하다고 생각합니다.  Rest 서비스가 아니더라도 exception 만큼은 잘 알고 코딩을 했으면 좋겠습니다. 

고급 개발자 또는 잘하는 사람의 코딩을 보면 굉장히 간결하고 짧게 코딩이 되어 있는 것을 볼 수 있습니다. 

코드를 간결하고 쉽게 만드는 비결 중 하나는 얼마만큼 exception을 다룰 수 있는가 하는 능력이라고 봅니다. 

코드의 반은 try-catch이고 나머지 반은 조건문인 경우가 많습니다. 이런 부분만 해소하면 코드 품질이 엄청나게 향상될거라 생각합니다.  

댓글
댓글쓰기 폼