[리팩토링] 예외 처리와 계층분리/책임분리

2025. 1. 4. 23:16
728x90

시큐리티 관련 예외처리를 파다 보니, 또 다른 문제가 보였다. 

바로,,, 예외 처리에 대한 계층 분리 문제.

 

현재 나의 코드의 문제점은 모든 예외상황에 같은 Exception을 던진다는 것이다. 

코드로 확인하면 이렇다.

// 프로필 사진 추가 
@Transactional
public String uploadProfileImage(String accountId, List<MultipartFile> multipartFile) {
	//...
    
    // s3에 해당 프로필 사진 업로드 (단 한장만 가능!!)
    if (multipartFile.size() > 1) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "프로필 사진은 한장만 업로드 가능");
    }
    // ...
}

// 관리자의 사업자 전환신청 거절/수락 기능
public BAResponse manageBusinessAccount(String accountId, BAManagement agreement) {
    // ...
    // 2. 신청자가 active or business상태인지 확인
    if (!foundUser.getAuthority().equals("ROLE_ACTIVE")) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "사업자 전환이 불가능한 계정입니다.");
    }
    // ...
}

이런 식으로 예외를 처리할 때 서비스 계층, 컨트롤러 계층과 같은 계층은 고려하지 않고 무조건 ResponseStatusException을 던졌다. 

 

하지만, 이렇게 처리를 하게 되면

 

1. 계층분리 원칙에 어긋난다. 

  • 비즈니스 로직은 HTTP 계층과 독립적으로 설계되어야 한다. 
  • ResponseStatusException은 HTTP와 직접 연관된 예외이기 때문에 컨트롤러 계층, 즉 HTTP 요청을 처리하는 곳에서만 사용해야 한다. 

2. 문제 디버깅유지보수에 문제가 생긴다. 

  • 무조건 ResponseStatusException을 사용하게 되면, 문제가 어디서 어떻게 발생했는지를 명확히 알기 어렵다. 컨트롤러 계층인지, 비즈니스 로직 계층인지도 확인이 불가하다. 

 

 

 

그러면 어떻게 해야할까?

 

 

1. 서비스 계층 : HTTP와 독립적인 예외를 던진다. 이를 위해서 사용자 정의 예외를 만들어 일반적인 예외 클래스보다 문제를 명확하게 표현한다. 

 

2-1. 컨트롤러 계층 : 비즈니스 로직에서 발생한 예외를 받아 적절한 HTTP 상태 코드와 메시지를 반환. 

혹은 

 

2-2. 글로벌 예외 처리 계층 : @RestControllerAdvice를 사용해 HTTP 상태 코드 매핑과 공통 처리 로직을 구현한다. 

 

 

<적용>

1. 사용자 정의 예외를 만들고, 실제 해당 예외가 발생하는 곳에서 예외를 메시지와 함께 던져준다. 

- RuntimeException을 상속받는 나만의 예외를 만들고, 메시지를 전달한다. 

public class InvalidBusinessAccountException extends RuntimeException {
    public InvalidBusinessAccountException(String message){
        super(message);
    }
}

- 서비스단에서 해당 예외가 발생하면 던져준다.

// 관리자의 사업자 전환신청 거절/수락 기능
public BAResponse manageBusinessAccount(String accountId, BAManagement agreement) {
    // ...
    // 2. 신청자가 active or business상태인지 확인
    if (!foundUser.getAuthority().equals("ROLE_ACTIVE")) {
        throw new InvalidBusinessAccountException("사업자 전환이 불가능한 계정입니다. ");
    }
    // ...
}

 

2-1. 이 서비스를 받는 컨트롤러 계층에서 try-catch구문으로 예외 발생시 HTTP 상태코드와 함께 오류 전달

    // 관리자의 사업자 전환신청 거절/수락 기능
    @PostMapping("/business/{accountId:.*}")
    public BAResponse manageBusinessAccount(
            @PathVariable("accountId") String accountId,
            @RequestBody BAManagement agreement
    ) {
    	try {
	        return adminService.manageBusinessAccount(accountId, agreement);
        } catch (InvalidBusinessAccountException ex) {
        	throw new ResponseStatusException(HttpStatus.BAD_REQUEST, ex.getMessage());
        }
    }

3-2. 글로벌 예외처리 핸들러를 만들어 오류와 HTTP 상태코드를 매핑해준다. 

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(InvalidBusinessAccountException.class)
    public ResponseEntity<String> handleInvalidBusinessException(InvalidBusinessAccountException ex) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
    }
}

 

 

이렇게 처리를 해보았더니 결과가 이렇게 나왔다. 

2-1. 컨트롤러에서 try-catch. 2-2. 글로벌 예외처리 핸들러 작성

 

 

그러나 아직도, 부족하다!!!!

예외처리가 미숙하다. 

 

이유는 이렇게 계속 커스텀 Exception 클래스를 만들다 보면 서비스가 커지면 커질수록 계속 아주 많은 예외 클래스를 만드느냐...? 

그것은 개발자의 숙명을 거스르는 것임...

 

이 경우에는 어떻게 상황을 개선시킬 수 있을지는 다음에 이어 적어보도록 하겠다!

728x90

BELATED ARTICLES

more