common: exception handling policy - takeoff-26/logistics-service GitHub Wiki

exception handling policy

MSA์—์„œ๋„ ์ „์—ญ์ ์ธ exception์— ๋Œ€ํ•œ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
exception์— ๋Œ€ํ•ด ๊ณตํ†ต๋œ ์ฒ˜๋ฆฌ, ๋ฐ˜ํ™˜๋˜๋Š” ์—๋Ÿฌ ์ฝ”๋“œ๋‚˜ ์‘๋‹ต ํ˜•์‹์„ ํ†ต์ผํ•ด์•ผ ํ•˜๋ฉฐ ๊ฐ๊ฐ ๋น„์ฆˆ๋‹ˆ์Šค์— ๋งž์ถฐ ํ•ด๋‹น ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•ด ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋˜๋Š” ๋ฉ”์‹œ์ง€๋Š” ๋‹ค๋ฅผ ๊ฒƒ์ด๋‹ค.
์‘๋‹ต๊ณผ ์ฝ”๋“œ์— ๋Œ€ํ•ด์„œ๋Š” ํ†ต์ผ์„ฑ์„ ๊ฐ€์ง€๋˜ ์‚ฌ์šฉ๋˜๊ณ  ๋ฐ˜ํ™˜๋˜๋Š” ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ๊ฐ’์„ ์ผ๊ด€์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๋ฒˆํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ์„ฑํ–ˆ๋‹ค.
๊ณตํ†ต์œผ๋กœ ์ ์šฉ๋  ๋ถ€๋ถ„์ด๋‹ค ๋ณด๋‹ˆ ์—ญ์‹œ Common์— ์œ„์น˜ํ•˜๊ฒŒ ๋˜์—ˆ๊ณ  ์ด๋ฒˆ์—๋Š” exception์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋” ์„ธ๋ถ„ํ™”ํ•ด ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
์—๋Ÿฌ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ํ๋ฆ„์€ ์ด์ „๊ณผ ๋น„์Šทํ•˜๋‹ˆ ๊ทธ๋ฆผ์œผ๋กœ ํ™•์ธํ•˜๊ณ  ๋กœ์ง์œผ๋กœ ์ง„ํ–‰ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.


image

@Getter
public class BusinessException extends RuntimeException {

	private final ErrorCode errorCode;

	public static BusinessException from(ErrorCode errorCode) {
		return new BusinessException(errorCode);
	}

	protected BusinessException(ErrorCode errorCode) {
		super(errorCode.getMessage());
		this.errorCode = errorCode;
	}
}


public record ErrorResponse(String code, String message, int status, List<ValidationError> errors) {

	static final String ILLEGAL_ERROR_CODE = "ILL_001";

	public static ErrorResponse of(ErrorCode errorCode) {
		return new ErrorResponse(
			errorCode.getCode(),
			errorCode.getMessage(),
			errorCode.getStatus(),
			Collections.emptyList()
		);
	}

	public static ErrorResponse of(String message, int status) {
		return new ErrorResponse(
			ILLEGAL_ERROR_CODE,
			message,
			status,
			Collections.emptyList()
		);
	}

	public static ErrorResponse of(BindingResult bindingResult) {
		return new ErrorResponse(
			VALIDATION_ERROR.getCode(),
			VALIDATION_ERROR.getMessage(),
			VALIDATION_ERROR.getStatus(),
			ValidationError.ofFieldErrors(bindingResult.getFieldErrors())
		);
	}

	public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
		return new ErrorResponse(
			CONSTRAINT_VIOLATION.getCode(),
			CONSTRAINT_VIOLATION.getMessage(),
			CONSTRAINT_VIOLATION.getStatus(),
			ValidationError.ofConstraintViolations(violations)
		);
	}

	public record ValidationError(
		String field,
		String rejectedValue,
		String reason
	) {

		private static List<ValidationError> ofFieldErrors(
			List<org.springframework.validation.FieldError> fieldErrors) {
			return fieldErrors.stream()
				.map(error -> new ValidationError(
					error.getField(),
					error.getRejectedValue() == null ?
						"" : String.valueOf(error.getRejectedValue()),
					error.getDefaultMessage()))
				.toList();
		}

		private static List<ValidationError> ofConstraintViolations(
			Set<ConstraintViolation<?>> constraintViolations) {
			return constraintViolations.stream()
				.map(violation -> new ValidationError(
					violation.getPropertyPath().toString(),
					violation.getInvalidValue() == null ?
						"" : String.valueOf(violation.getInvalidValue()),
					violation.getMessage()))
				.toList();
		}
	}
}

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(BusinessException.class)
	public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
		ErrorResponse errorResponse = ErrorResponse.of(e.getErrorCode());
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}

	@ExceptionHandler(MethodArgumentNotValidException.class)
	public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(
		MethodArgumentNotValidException e) {
		ErrorResponse errorResponse = ErrorResponse.of(e.getBindingResult());
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}

	@ExceptionHandler(ConstraintViolationException.class)
	public ResponseEntity<ErrorResponse> handleConstraintViolationException(
		ConstraintViolationException e) {
		ErrorResponse errorResponse = ErrorResponse.of(e.getConstraintViolations());
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}

	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
	public ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(
		HttpRequestMethodNotSupportedException e) {
		log.error(e.getMessage(), e);
		ErrorResponse errorResponse = ErrorResponse.of(METHOD_NOT_ALLOWED);
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}

	@ExceptionHandler(Exception.class)
	public ResponseEntity<ErrorResponse> handleException(Exception e) {
		log.error(e.getMessage(), e);
		ErrorResponse errorResponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}

	@ExceptionHandler(IllegalStateException.class)
	public ResponseEntity<ErrorResponse> handleIllegalStateException(IllegalStateException e) {
		ErrorResponse errorResponse = ErrorResponse.of(e.getMessage(), HttpStatus.CONFLICT.value());
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}

	@ExceptionHandler(IllegalArgumentException.class)
	public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) {
		ErrorResponse errorResponse = ErrorResponse.of(e.getMessage(), HttpStatus.BAD_REQUEST.value());
		return ResponseEntity.status(errorResponse.status()).body(errorResponse);
	}
}


public interface ErrorCode {

	String getCode();
	String getMessage();
	int getStatus();
}


์œ„ ErrorCode๋ฅผ ์ƒ์† ๋ฐ›์•„ ๊ฐ ๋น„์ฆˆ๋‹ˆ์Šค์—์„œ ๋ณธ์ธ์˜ ์—๋Ÿฌ์ฒ˜๋ฆฌ์— ๋งž๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์„ค์ •ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.