본문 바로가기
프로젝트/개인 프로젝트) 요만큼

refactor ) 예외 상황을 좀 더 멋지게 처리해보자!

by 휴일이 2024. 2. 1.

 

 

사실 이전에 예외 상황을 처리하는 코드를 리팩토링했다.

 

그러나

1. Checked 예외를 만들면 그에 따른 Handler 를 계속 추가해줘야하는 불편함이 있다.

2. 응답이 200 OK 라면 굳이 Body 에 정상 응답이라는 내용을 담아줄 필요는 없다.

3. 서버에서는 정말 필요한 응답값 (에러 상황에 필요한 메시지와 코드) 만 응답해도 좋겠다.

라는 생각을 토대로, 코드를 수정해보기로 했다!

 

 

 

기존 코드

기존 로그인 컨트롤러

    @PostMapping
    @Operation(summary = "일반 회원 로그인", description = "일반 회원용 로그인")
    public ResponseEntity<Response> login(@RequestBody @Valid LoginDto loginDto) throws UserNotFoundException {

        Map<Tokens, String> tokens = userService.login(loginDto);

        return Response.ok(ResponseCode.OK, tokens);
    }

 

기존 로그인 컨트롤러는

UserNotFoundException 이라는 Checked 예외를 던지고 있고

요청이 제대로 처리됐다면 요청이 정상 처리되었다는 데이터들을 반환한다.

 

 

기존 로그인 서비스

    @Override
    public Map<Tokens, String> login(LoginDto loginDto) throws UserNotFoundException {

        String email = loginDto.getEmail();
        String password = loginDto.getPassword();

        User findUser = userRepository.findByEmailFetchRole(email)
                .orElseThrow(() -> new UserNotFoundException(ResponseCode.USER_NOT_FOUND));
        String findUserPassword = findUser.getPassword();

        boolean pwdMatches = passwordEncoder.matches(password, findUserPassword);

        if (!pwdMatches) {
            log.error("비밀번호가 안 맞음");
            throw new UserNotFoundException(ResponseCode.USER_NOT_FOUND);
        }

        log.info("아이디 비밀번호 일치 : {}", email);

        String accessToken = tokenService.creatToken(findUser.getId(), findUser.getNickname(), findUser.getRole().getRoleName());
        String refreshToken = tokenService.createRefreshToken();

        Map<Tokens, String> tokenMap = new HashMap<>();
        tokenMap.put(Tokens.ACCESS_TOKEN, accessToken);
        tokenMap.put(Tokens.REFRESH_TOKEN, refreshToken);

        return tokenMap;
    }

 

기존 로그인 서비스에서는 유저가 없거나 비밀번호가 틀렸을 경우 내가 만든 UserNotFoundException(Checked 예외) 를 발생시킨다.

- UserNotFoundException 은 Exception 을 상속받고

- 생성자로 만들어질 때 ResponseCode 를 초기화 시킨다.

 

그러면 핸들러에는 

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<Response> userNotFoundHandler(UserNotFoundException e) {
        log.error("유저 에러 : {}", e.getResponseCode().getMessage());
        e.printStackTrace();
        return Response.badRequest(e.getResponseCode());
    }

ResponseCode 의 메시지를 보여주고, ResponseCode 를 Response 객체에 담아준다.

 

 

그러면

@Getter
@Builder
public class Response {

    private String message;
    private HttpStatus status;
    private ResponseCode code;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime timestamp;
    private Object data;

    public static ResponseEntity<Response> ok(ResponseCode responseCode) {
        return response(HttpStatus.OK, responseCode);
    }
    public static ResponseEntity<Response> ok(ResponseCode responseCode, Object data) {
        return responseWithData(HttpStatus.OK, data, responseCode);
    }

    public static ResponseEntity<Response> badRequest(ResponseCode responseCode) {
        return response(HttpStatus.BAD_REQUEST, responseCode);
    }

    public static ResponseEntity<Response> badRequest(ResponseCode responseCode, Object data) {
        return responseWithData(HttpStatus.BAD_REQUEST, data, responseCode);
    }

    private static ResponseEntity<Response> responseWithData(HttpStatus status, Object data, ResponseCode responseCode) {
        return ResponseEntity.status(status).body(
                Response.builder()
                        .status(status)
                        .message(responseCode.getMessage())
                        .timestamp(LocalDateTime.now())
                        .data(data)
                        .code(responseCode)
                        .build()
        );
    }

    private static ResponseEntity<Response> response(HttpStatus status, ResponseCode responseCode) {
        return ResponseEntity.status(status).body(
                Response.builder()
                        .status(status)
                        .message(responseCode.getMessage())
                        .timestamp(LocalDateTime.now())
                        .code(responseCode)
                        .build()
        );
    }

}

 

이런식으로 구성된 Response 가 생기고,

message 와 code 에 ResponseCode 값들이 담겨서 반환되게 되는 것이다.

 

 

 

 

바뀐 코드

 

바뀐 코드는

1. 핸들러가 없음.

2. Response 객체 필요 없음.

3. ResponseCode 필요 없음.

 

 

 

 

바뀐 로그인 서비스

    @Override
    public Map<Tokens, String> login(LoginDto loginDto) {

        String email = loginDto.getEmail();
        String password = loginDto.getPassword();

        User findUser = userRepository.findByEmailFetchRole(email)
                .orElseThrow(() -> new BadRequestException(Exception.USER_NOT_FOUND));
        String findUserPassword = findUser.getPassword();

        boolean pwdMatches = passwordEncoder.matches(password, findUserPassword);

        if (!pwdMatches) {
            log.error("비밀번호가 안 맞음");
            throw new BadRequestException(Exception.USER_NOT_FOUND);
        }

        log.info("아이디 비밀번호 일치 : {}", email);

        String accessToken = tokenService.creatToken(findUser.getId(), findUser.getNickname(), findUser.getRole().getRoleName());
        String refreshToken = tokenService.createRefreshToken();

        Map<Tokens, String> tokenMap = new HashMap<>();
        tokenMap.put(Tokens.ACCESS_TOKEN, accessToken);
        tokenMap.put(Tokens.REFRESH_TOKEN, refreshToken);

        return tokenMap;
    }

 

UserNotFoundException(Checked) 을 BadRequestException(UnChecked) 으로 바꿔서 던져준다.

 

 

 

그리고 ResponseCode 대신 Exception 객체를 사용해 에러 코드를 관리하는데

Exception 객체를 자세히 보자(java 제공 Exception 클래스 아님;;)

 

Exception

@Getter
public enum Exception {

    // 가계부
    ACCOUNT_BOOK_NOT_FOUND("가계부를 찾을 수 없습니다.", 404),
    RECORD_BOOK_NOT_FOUND("내역을 찾을 수 없습니다.", 404),
    
    // 유저
    USER_NOT_FOUND("유저를 찾을 수 없습니다.", 404),
    
    // 이메일
    EMAIL_NOT_FOUND("이메일을 찾을 수 없습니다.", 404),
    EMAIL_CODE_UN_MATCHED("이메일 코드가 다릅니다.", 404),
    
    // OAuth2
    SNS_NOT_FOUND("존재하지 않는 SNS입니다.", 404),
    OAUTH_PUBLIC_KEY_UN_MATCHED("OAuth2 공개 키가 다릅니다", 500);

    private final String message;
    private final int code;

    Exception(String message, int code){
        this.message = message;
        this.code = code;
    }
}

 

보며는 message, code 정도를 상태로 둔다.

 

 

BadRequestException 을 확인하면

public class BadRequestException extends RuntimeException{

    private final int code;

    public BadRequestException(Exception exception){
        super(exception.getMessage());
        code = exception.getCode();
    }

}

 

Exception 객체를 받아 메시지를 RuntimeException 에 넘겨주고 상태 코드정도만 받아둔다.

그러면 예외가 발생했을 때 메시지는 우리가 만든 메시지가 뜨겠죵?

 

 

 

그리고 마지막으로

정상 응답이 됐을 땐, ResponseEntity<Response> 로 응답 상태 + 보내는 객체를 주는 것이 아닌

 

    @PostMapping
    @Operation(summary = "일반 회원 로그인", description = "일반 회원용 로그인")
    public Map<Tokens, String> login(@RequestBody @Valid LoginDto loginDto) {
        return userService.login(loginDto);
    }
    
    @PostMapping
    @Operation(summary = "일반 회원 가입", description = "일반 회원 가입 완료")
    public void signUp(@RequestBody @Valid UserSignUpDto userSignUpDto) {
        userService.signUp(userSignUpDto);
    }

 

응답 값이 있을 땐 객체 자체를 반환, 없을 땐 void 로 반환 값이 없게 했다.

어차피 200 OK 라면 굳이 응답 바디에 정상 응답이라는 정보를 넣어주지 않아도 될 것 같았기 때문이당. ㅎㅅㅎ/)

 

어쨌든 이런 식으로...리팩토링 중이라는 이야기,,,였음니다..

그래서 이렇게 핸들러와 Exception 들을 하나하나 삭제중이란 얘기~

728x90