[GraphQL] Exception Handling

본 포스트는 공식 레퍼런스를 참고해 GraphQL을 공부하며 직접 작성한 가이드 입니다.
본 포스트는 2021년 7월 최신 버전인 v16.2를 기준으로 작성되어 있습니다.

Exception Handling

Spring에서는 예외를 잘 다루기 위해서 @ControllerAdvice와 같은 전역 예외 처리를 지정해줄 수 있었다. 예외가 발생하면 해당 예외에 따라 적절한 메세지를 내려주는 등의 정의를 GraphQL에서도 할 수 있다. 간단하게 ImageResolver에서 예외를 발생시켜 보자.

1
2
3
4
5
6
7
8
@Slf4j
@Component
public class ImageResolver implements GraphQLResolver<WebToon> {
public DataFetcherResult<Image> image(WebToon webToon, DataFetchingEnvironment env) {
log.info("requesting image data for webtoon id: {}", webToon.getId());
throw new RuntimeException(String.format("No Images on webtoon title : %s", webToon.getTitle()));
}
}

그러면 다음과 같은 응답이 떨어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"errors": [
{
"message": "Internal Server Error(s) while executing query",
"locations": []
}
],
"data": {
"webToon": {
"id": "3568e088-ec83-11eb-9a03-0242ac130003",
"title": "여신강림",
"webToonType": "Origianls",
"image": null
}
}
}

우리는 No Images on webtoon title : %s의 포멧을 가진 응답이 떨어지기를 기대했는데 Internal Server Error(s) while executing query라는 예외 메세지가 떨어졌다. 이제 이를 고쳐서 우리 스타일의 응답을 만들기 위해 GraphQL용 예외 처리기를 만들어보자.

WebToonException.class

일단 더 명확한 예외를 던지기 위해 WebToonException.class를 만들어준다.

1
2
3
4
5
6
7
8
9
10
11
12
public class WebToonException extends RuntimeException {
private WebToonErrorCode errorCode;

public WebToonException(String message, WebToonErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}

public WebToonErrorCode getErrorCode() {
return errorCode;
}
}

조금 더 디테일한 에러를 위해 errorCode를 정의해주자.

1
2
3
4
5
6
7
8
9
10
11
@AllArgsConstructor
@Getter
public enum WebToonErrorCode {
NO_TITLE(1001, "No Title Exsist"),
NO_IMAGE(1002, "No Image Exsist"),
UNKNOWN(9999, "Unknown Exception")
;

private int errorCode;
private String errorMessage;
}

그리고 우리가 정의한 예외를 던지도록 하자.

1
throw new WebToonException(String.format("No Images on webtoon title : %s", webToon.getTitle()), WebToonErrorCode.NO_IMAGE);

@ExceptionHandler

GraphQL에서 예외 처리를 위한 여러 방법들이 있는데 먼저 @ExceptionHanlder를 사용하는 방법부터 해보자. 먼저 예외처리를 위한 빈을 등록해준다. @GraphQLAdvice는 직접 만든 어노테이션인데 일반 @Bean으로 등록해도 상관없다.

1
2
3
4
5
6
7
8
9
10
@Slf4j
@GraphQLAdvice
public class GraphQLExceptionAdvice {
@ExceptionHandler(WebToonException.class)
public WebToonError handle(WebToonException e) {
log.error(String.format("Webtoon Exception caused by [%s], errorCode : [%s]", e.getMessage(), e.getErrorCode()));

return new WebToonError(e);
}
}

코드를 보면 WebToonException을 잡아서 WebToonError로 변경해준다.

WebToonError.class의 구현을 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class WebToonError implements GraphQLError {
private WebToonException e;

public WebToonError(WebToonException e) {
this.e = e;
}

@Override
public String getMessage() {
return e.getErrorCode().getErrorMessage();
}

@JsonIgnore
@Override
public List<SourceLocation> getLocations() {
return null;
}

@JsonIgnore
@Override
public ErrorClassification getErrorType() {
return null;
}
}

WebToonError.classGraphQLError를 상속하고 메세지로 에러코드의 메세지를 내려준다.

1
2
3
graphql:
servlet:
exception-handlers-enabled: true

그리고 마지막으로 exception handler를 활성화 한 뒤 요청을 보내고 응답을 봐보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"errors": [
{
"message": "No Image Exsist"
}
],
"data": {
"webToon": {
"id": "3568e088-ec83-11eb-9a03-0242ac130003",
"title": "여신강림",
"webToonType": "Origianls",
"image": null
}
}
}

우리가 정의한 에러코드 메세지인 No Image Exsist가 잘 내려온다.

GraphQL답게 예외 처리하기

GraphQLError 인터페이스는 예외에 대한 다양한 정보를 담아 내려주는 메소드들을 정의한다. 이를 통해 발생한 예외에 대해 상세하고 다양한 정보를 담을 수 있다. 먼저 우리가 만들어둔 WebToonException.class를 리팩토링하자.

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
27
public class WebToonException extends GraphQLException implements GraphQLError {
private WebToonErrorCode errorCode;

public WebToonException(String message, WebToonErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}

public WebToonErrorCode getErrorCode() {
return errorCode;
}

@Override
public List<SourceLocation> getLocations() {
return null;
}

@Override
public ErrorClassification getErrorType() {
return null;
}

@Override
public Map<String, Object> getExtensions() {
return WebToonExceptionHelper.makeExtensions(this);
}
}

WebToonExceptionHelper에 추가로 정의될 메세지들을 넣었다.

1
2
3
4
5
6
7
8
9
10
public class WebToonExceptionHelper {
public static Map<String, Object> makeExtensions(WebToonException e) {
Map<String, Object> extensions = new LinkedHashMap<>();

extensions.put("errorCode", e.getErrorCode().getErrorCode());
extensions.put("errorMessage", e.getErrorCode().getErrorMessage());

return extensions;
}
}

이제 WebToonExceptionGraphQLError의 역할까지 수행하니 exception-handlers-enabled를 다시 false로 바꿔주고 응답을 보자.

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
27
28
29
30
{
"errors": [
{
"message": "Exception while fetching data (/webToon/image) : null",
"locations": [
{
"line": 6,
"column": 5
}
],
"path": [
"webToon",
"image"
],
"extensions": {
"errorCode": 1002,
"errorMessage": "No Image Exsist",
"classification": "DataFetchingException"
}
}
],
"data": {
"webToon": {
"id": "3568e088-ec83-11eb-9a03-0242ac130003",
"title": "여신강림",
"webToonType": "Origianls",
"image": null
}
}
}

extensions에 필요한 에러코드와 에러메세지가 내려오는걸 확인할 수 있다. 또한 어느 위치에서 에러가 났는지에 대한 상세한 정보도 GraphQL spec에 따라 내려오는걸 볼 수 있다.

Repository

모든 가이드의 예제 코드는 SongHayoung/springboot-graphql-tutorial에서 확인할 수 있습니다.

Author: Song Hayoung
Link: https://songhayoung.github.io/2021/07/25/GraphQL/graphql-6/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.