web api 서비스 application에서 예외 (오류와 거절)

외부에 제공하는 api

오픈 api처럼 외부에 api를 제공하고, api 명세서 제공한다.
이경우 api의 스팩이 정해져있고 다양한 clientem들이 이 규칙에 따라서 호출하게된다.
api는 성공하는경우가 대부분이겠지만 상황에 따라서 실패하기도 하고 이에따라 실패 메시지를 받게된다.
실패에도 다양한 경우가 있다.

이포스트는 여러가지 실패한 상황과 예외에 대해 다뤄보려고한다

관련포스트

프로그래밍에서 예외 - 기초 개념
java에서 exception
wep api 서비스 application에서 exception

예시

아래는 카카오-친구에게보내기 api의 예시이다.
https://developers.kakao.com/docs/latest/ko/message/rest-api#send-friend

HTTP/1.1 200 OK

1
2
3
4
5
6
7
8
{
"successful_receiver_uuids": ["abcdefg0001","abcdefg0002"],
"failure_info":[{
"code": -532,
"msg": "daily message limit per sender has been exceeded.",
"receiver_uuids": ["abcdefg0003"]
}]
}

일반적으로 응답코드와 메시지가 포함되어 response를 전달하게된다.
이처럼 실패한경우에도 코드와 메시지를통해 구분가능하다.
이 api를 사용하는 클라이언트들이 있고, 코드와 메시지가 정해져있다면 변경하기 쉽지않다.

정해진 응답값

응답값은 클라이언트와의 약속이다. 한번 정해지면 바꾸기 쉽지 않다.
이렇게 변하지 않는값으로 간주된다면 enum을 통해서 관리하는것도 방법이다.

1
2
3
4
5
6
7
8
9
10
public enum ResponseCode {
SUCCESS(0, "성공"),
NOT_EXIST(1001, "존재하지 않음"),
INVALID_STATE(1004, "올바르지 않은 상태"),
UNKNOWN(-1, "알 수 없는 오류");
...

private final int code;
private final String message;
}

실패 vs 거절 vs 예외

성공 응답이 아닌 다른 값들이 전달되었다면 이것은 오류라고 볼 수 있을까?
성공 응답값을 주지 못하는 상황에서 어떠한 대처가 되어있는지에 따라서 거절인지 예외인지 다르게 부를 수 있을것이다.

예시

id를 input으로 받아 이름을 output으로 받는 api가 있다고 가정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping(/)
public Response<String> getUserName(String userId){
User user = getUser();

if(user == null){
return Response.builder()
.code(ResponseCode.NOT_EXIST.getCode())
.message(ResponseCode.NOT_EXIST.getMessage())
.build();
}

return Response.builder()
.code(ResponseCode.SUCESS.getCode())
.message(ResponseCode.SUCESS.getMessage())
.data(user.getName());
.build();
}

요청이 올바르지 않거나, 원하는 데이터가 없을경우에도 요청한 client에게는 HTTP STATUS 200 OK 가 내려가며 이는 정상 응답값이다.
위 예시에 있는 NOT_EXIST(1001, "존재하지 않음") 는 이미 유저가 없는 경우를 예상한 플로우라고 볼 수 있다. application상의 로직으로 구현되어 정상적인 분기처리후 해당 응답이 내려가게된다.

실패 의 이유는 다양할수 있으나 거절 은 로직으로 판단하여 수행된 application의 정상로직이다.

실패

실패의 원인은 다양하다. 성공하지 못하면 모두 실패이다.
위 사례처럼 정당한 사유로 거절된 경우도 있을것이다. 하지만 모든 상황을 예상할수는 없다.
원치 않는 상황이 발생하여 각종 예외(Exception)가 발생할 수 있고, 이를 재대로 대처하지 못했다면 오류가 되어500 internal server error 가 발생하게된다.

예외

예외(Exception)는 java 언어에서 사용할수있는 구현 수단이다. 이를 잘 활용하고 대응하여 거절 로 응답을 내려 줄 수 있다. 예외를 잘 관리하지 못한다면 오류가되어 원하지 않은 응답이 내려가게 될것이다.

직접 예외를 만들고 이를 핸들링하여 예외를 활용하여 거절응답을 하도록 구현 하는 방법도 많이 사용한
다.

분기와 예외

거절 응답을 내려주기위해서 분기처리를 해도 되지만, try-catch 또는 exception handler 를 활용한 방법도 있을것이다. 분기처리는 if-else 구문으로 정해진상황을 파악하여 거절응답을 내려주는 방법이다.
본 포스트에서는 분기보다는 예외에 초점을 맞추어 설명한다.

예외를 활용하는 가장 큰 이유는 @Transactional 이다. 수행중인 서비스가 데이터의 변경을 가지고오지않는다면 정해진 거절응답만 하면 해당 요청에 대한 응답은 마친샘이다.

비교예시

아래의 예시 경우라면 0으로 나누기전 3개의 데이터는 repository에 저장될수있다. 일관성을위해 데이터가 저장되면 안되는 상황이라면 rollback시킬 필요가있다.

1
2
3
4
5
6
7
8
//use exception
@Transactional
public divideAndSave (Integer value, List<Integer> datas){
// value = 6, datas = [1,2,3,0]
for(Integer data : Datas){
repository.save(value / data); // 6/0 에서 DivideByZeroException
}
}
  • try-catch 라면 @Transactional 의도움을 받아 transaction이 실행되기 전으로 되돌려 저장되지 않도록 할수있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//use if-else
@Transactional
public divideAndSave (Integer value, List<Integer> datas){

List<Integer> keys = new ArrayList();
// value = 6, datas = [1,2,3,0]
for(Integer data : Datas){
if(data == 0){
repository.removeAll(keys);
}
int key = repository.save(value / data); // 6/0 에서 DivideByZeroException
keys.add(key);
}
}
  • if-else 구문이라면 저장된 데이터의 key를 기억했다가 이를 모두 제거해야하지만

주의사항

단, try-catch를 사용한다면 주의해야한다.
AException이 발생하여 A영역의 catch에 들어가게되면 데이터는 rollback되지않는다.
catch에 해당하는 로직만 수행된다.
BException의경우 내부에서 한번더 예외를 발생시켜 transactional에 의한 rollback기능이 수행된다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//use exception
@Transactional
public divideAndSave (Integer value, List<Integer> datas){
// value = 6, datas = [1,2,3,0]
try{
for(Integer data : Datas){
repository.save(value / data); // 6/0 에서 DivideByZeroException
}
} catch (AException e){
//A
log.error("...");
} catch (BException e){
//B
log.error("...");
throw e;
}
}

결론

  • 발생할수 있는 상황을 가능한 고려하여 오류가아닌 거절이 되도록 해야한다.
  • 거절을하는 과정에서 구현상 분기를 사용해도되고 CustomException를 활용할 수 있다.
    • custom exception 과 예상치못한 진짜 exception을 구분하여 발견/수정할수 있어야한다
  • 외부에서는 내부구현을 알 수 없다. 어떠한 상황이던 client에서 적절한 응답을 받을 수 있게 대응하자.

Comments