작업 일지#11 배포준비
프로젝트의 완성도는 아직 많이 부족하지만, 우선은 배포를 진행 해보기로 했다. 부족한 부분은 배포를 한 후에도 계속 적용이 가능하고, 오히려 완성이 덜된 상태에서 배포를 하고, 수정을 하는 과정에서 CI/CD를 구축하고 통합 및 배포의 자동화가 제대로 적용되는지 확인할 수 있는 요소가 될 수 있으니, 우선은 배포 환경을 구성하는 쪽으로 진행해볼까 한다.
따라서, 현 시점에서는 도메인 서비스 구현을 멈추고, 대략적으로 마무리를 하고자 한다. 먼저 마무리 되어야하는 부분은 예외처리를 에러 메세지로 연결하는 작업일 듯 하다.
SpringMVC에서는 기본적으로 예외가 발생하면, 자동으로 메세지를 처리해준다. 이 기능만 놓고보면, 개발자는 그냥 맘편히 비즈니스 로직을 짜며, 예외처리를 하기만 하면, 다 될 듯 하다. 그러나, 여러가지의 이유로 예외처리를 하듯이, HTTP 역시 여러가지 이유로 에러가 발생한다. 따라서, 개발자는 상황에 맞게 예외처리를 함과 동시에 상황에 맞는 HTTP 에러메세지를 보내야한다. 하지만, 기본적으로 SpringMVC에서는 비즈니스로직으로 인해 발생한 예외상황은 전부 일괄적으로 HTTP 500 에러처리를 하기 때문에, 잘못된 인증정보 입력, 혹은 없는 리소스를 요청했을 경우와 같이 명백하게 클라이언트의 잘못된 요청으로 인해 발생한 에러를 구분하는 게 어려워진다.
따라서, 개발자가 특수한 상황에 대비하기 위해 따로 에러 메세지 처리를 해야하는데, HTTP API의 경우, ResponseEntity
를 활용해볼 수 있다. ResponseEntity에는 내부에 builder api를 제공하여, 정상적인 응답, 혹은 에러 메세지 응답을 직관적으로 활용할 수 있다. 따라서, 클라이언트의 잘못된 요청으로 인해 발생한 예외를 감지하여, 에러메세지를 리턴하는 로직을 Spring 프레임워크를 활용해서 리턴하자면, 다음과 같이 쓸 수 있겠다.
@PostMapping("auth")
public ResponseEntity<AuthenticationResultDto> authentication(@RequestBody AuthenticationRequest authRequest) throws
UnauthenticatedMemberException {
try {
SimpleAuthenticationToken authToken = new SimpleAuthenticationToken(
authRequest.getPrincipal(), authRequest.getCredential());
Authentication authentication = authenticationManager.authenticate(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
return ResponseEntity.ok(new AuthenticationResultDto((AuthenticationResult) authentication.getDetails()));
} catch (AuthenticationException e) {
return ResponseEntity.badRequest().build();
}
}
하지만, 위의 경우처럼 에러를 처리할 경우, 핸들러 메소드가 많아짐에 따라, 일일이 예외를 타이핑하며 처리해야하기 때문에, 코드가 매우 지저분해진다.
그래서 나의 경우.. @RestControllerAdvice
를 활용하였다. 의미를 정의하기위해 api 문서의 일부를 가져왔다.
@RestControllerAdvice is processed if an appropriate HandlerMapping-HandlerAdapter pair is configured such as the RequestMappingHandlerMapping-RequestMappingHandlerAdapter pair which are the default in the MVC Java config and the MVC namespace.
내용을 요약해보면, 핸들러를 매핑하고, 적절한 핸들러 어댑터가 페어링되었을 때, 작동하는 것이 @RestControllerAdvice
의 역할인 것이다. 즉, 클라이언트의 요청 url에 맞게 컨트롤러 메소드가 선택된 시점에서 작동한다는 것인데, 결과적으로는 컨트롤러의 메소드가 실행되기 전에 @RestControllerAdvice
가 실행된다. 다만 이 애노테이션은 aop를 기반으로 구현이 되어있는데, aop의 경우 리소스 비용이 매우 비싼 작업이므로, 모든 컨트롤러 일괄적으로 실행시키는 것은 낭비이다. 그래서 어드바이스를 먼저 실행할 조건을 형성하면 낭비를 줄일 수 있는데, 대표적으로 @RequestMapping
혹은 @ExceptionHandler
와 같이 일반적으로 컨트롤러 메소드를 매핑하는 것과 동일하게 진행할 수 있다.
나의 경우는 예외를 감지하여, 적절한 에러 메세지를 리턴하는 것이기 때문에 다음과 같이 코드를 작성했다.
@RestControllerAdvice
@Slf4j
public class ServiceExceptionHandlerControllerAdvice {
@RequestMapping(produces = "application/json")
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handlingException(RuntimeException exception) throws RuntimeException {
ErrorResponse errorResponse = ErrorResponse.configure(exception);
log.warn("Unexpected exception occurred", exception);
if (errorResponse.inComplete()) {
throw exception;
}
return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(errorResponse.getStatusCode()));
}
}
설명을 해보면, RuntimeException이 감지되면, handlingException
가 실행되어, 에러 메세지를 리턴하는 메소드이다.
우선은 이 코드를 기반으로, 서비스 레이어에서 작업한 비즈니스 로직에서 발생한 예외처리를 위의 메소드 하나로 에러 메세지로 바로 연동이 가능할 것이다. 물론 더 좋은 방법이 있겠지만, 기존에 핸들러 메소드를 하나하나씩 일일이 예외처리를 하는 것보다는 효율적일 것 같다.