1. 예외가 많아질 때 발생하는 문제
서비스를 구현하다 보면 다양한 예외 상황이 발생한다.
예를 들어 로그인만 하더라도 다음과 같은 경우가 있을 수 있다.
| 상황 | 예외 |
| 아이디가 존재하지 않음 | NotFoundUsername |
| 비밀번호가 일치하지 않음 | MismatchPassword |
| 블랙리스트 사용자 | BlacklistUsername |
| 인증 대기 상태 | NotAuthentication |
예외 상황마다 별도의 클래스를 만드는 것이 정석적인 방법이 될 수 있다.
하지만 작은 프로젝트에서 모든 예외 케이스마다 클래스를 만드는 것은 오히려 복잡도를 높일 수 있다.
따라서 프로젝트 규모에 따라 적절한 예외 처리 방식을 선택할 필요가 있다.
2. RsData를 이용한 응답 형식
RsData는 API 응답을 일정한 형식으로 전달하기 위해 사용하는 클래스이다.
public class RsData<T> {
private String resultCode;
private String msg;
private T data;
public RsData(String resultCode, String msg) {
this.resultCode = resultCode;
this.msg = msg;
this.data = null;
}
}
T를 제네릭으로 사용하면 RsData를 사용하는 시점에 data의 타입을 결정할 수 있다.
예외 응답처럼 별도의 데이터를 반환할 필요가 없는 경우에는 RsData<Void>로 사용할 수 있다.
3. ServiceException으로 서비스 예외 통일하기
작은 프로젝트에서는 서비스나 도메인에서 발생하는 예외를 ServiceException 하나로 통일하고, resultCode와 message를 통해 구체적인 예외 상황을 구분할 수 있다.
예를 들어 비밀번호가 일치하지 않는 경우 다음과 같이 예외를 발생시킬 수 있다.
if (!actor.getPassword().equals(password)) {
throw new ServiceException(
"401-1",
"비밀번호가 일치하지 않습니다."
);
}
이렇게 하면 별도의 MismatchPasswordException 클래스를 만들지 않아도 된다.
ServiceException은 다음과 같이 작성할 수 있다.
public class ServiceException extends RuntimeException {
private RsData rsData;
public ServiceException(String resultCode, String message) {
super(message);
this.rsData = new RsData(
resultCode,
message
);
}
public String getResultCode() {
return rsData.getResultCode();
}
public String getMsg() {
return rsData.getMsg();
}
}
예외 객체가 응답에 필요한 resultCode와 message를 함께 가지고 있도록 구성한 것이다.
4. GlobalExceptionHandler에서 예외 처리하기
컨트롤러에서 발생한 ServiceException을 각각 처리하지 않고, 전역 예외 처리기에서 한 번에 처리할 수 있다.
@ExceptionHandler(ServiceException.class)
@ResponseBody
public RsData<Void> handleException(ServiceException e) {
return new RsData<>(
e.getResultCode(),
e.getMsg()
);
}
@ExceptionHandler(ServiceException.class)를 통해 ServiceException이 발생했을 때 해당 메서드가 실행되도록 한다.
따라서 컨트롤러에서는 예외를 직접 응답으로 변환할 필요 없이 throw만 하면 된다.
Controller
↓
ServiceException 발생
↓
GlobalExceptionHandler
↓
RsData<Void> 형태로 응답
5. resultCode를 이용한 응답 코드 관리
resultCode를 "401-1"과 같은 형태로 관리하면 앞부분을 HTTP 상태 코드로 활용할 수 있다.
@JsonIgnore
public int getStatusCode() {
return Integer.parseInt(
resultCode.split("-")[0]
);
}
예를 들어 다음과 같이 구분할 수 있다.
| resultCode | HTTP 상태 코드 | 의미 |
| 200-1 | 200 | 성공 |
| 201-1 | 201 | 생성 성공 |
| 400-1 | 400 | 잘못된 요청 |
| 401-1 | 401 | 인증 실패 |
| 404-1 | 404 | 리소스를 찾을 수 없음 |
| 500-1 | 500 | 서버 오류 |
-1, -2와 같은 뒷부분을 이용하면 같은 HTTP 상태 코드 안에서도 구체적인 상황을 구분할 수 있다.
6. 왜 모든 예외마다 클래스를 만들지 않을까?
예외 상황이 많아질수록 예외 클래스도 계속 증가한다.
NotFoundUsername
MismatchPassword
BlacklistUsername
NotAuthentication
...
작은 프로젝트에서는 이런 구조가 오히려 관리하기 어려울 수 있다.
따라서 프로젝트 규모가 작다면 다음과 같이 구성할 수 있다.
서비스/도메인 예외
↓
ServiceException 하나로 통일
↓
resultCode + message로 세부 상황 구분
↓
GlobalExceptionHandler에서 일괄 처리
반대로 프로젝트가 커지고 예외마다 서로 다른 처리 로직이나 추가 정보가 필요해진다면 구체적인 예외 클래스를 분리하는 것이 적절하다.
'BackEnd > Spring' 카테고리의 다른 글
| [Spring] 로그인 기능 구현하기 (0) | 2026.09.02 |
|---|---|
| [Spring] API Key를 이용한 인증 처리 (0) | 2026.09.01 |
| Spring Boot 프로젝트 Railway 배포하기 + 배포 과정에서 발생한 오류 해결 (0) | 2026.08.27 |
| [Spring] CORS 설정 방법 | REST API와 프론트엔드 연결하기 (0) | 2026.08.19 |
| [Spring] SpringDoc으로 REST API 문서 자동화하기 - Swagger UI 사용법 (0) | 2026.08.14 |
댓글