{
"success": true,
"data": {
// 실제 반환하는 데이터 (객체, 리스트 등)
"id": 1,
"name": "사용자명",
"email": "user@example.com"
},
"error": null
}
{
"success": true,
"data": null,
"error": null
}
{
"success": false,
"data": null,
"error": {
"code": "USER-001",
"message": "사용자를 찾을 수 없습니다",
"status": 404
}
}
우리는 ok()
, ok(data)
, error(errorResponse)
이렇게 세 가지 정적 팩토리 메소드를 제공해서 성공/에러 응답을 전달함
Data d = Date.from(instant);
Data d = List.of(1, 2, 3);
Data d = BigInteger.valueOf(42);
객체 생성은 둘 다 일어나지만, 정적 팩토리 메서드는 그 생성 로직을 캡슐화해서 사용하는 쪽의 코드를 깔끔하게 해준다는 장점이 있음
// 매번 이렇게 생성해야 함
CommonResponseDto<Object> response = new CommonResponseDto.CommonResponseDtoBuilder<Object>()
.success(false)
.data(null)
.error(errorResponse)
.build();
// 이렇게 간단하게!
CommonResponseDto<Object> response = CommonResponseDto.error(errorResponse);
// 기본 에러 메시지 사용
ErrorResponse response1 = ErrorResponse.of(ErrorCode.USER_NOT_FOUND);
// 커스텀 에러 메시지 사용
ErrorResponse response2 = ErrorResponse.of(
ErrorCode.VALIDATION_ERROR,
"이메일 형식이 올바르지 않습니다."
);
// 인터페이스 기반 공통 에러 코드
public enum CommonErrorCode implements ErrorCode {
INVALID_INPUT(400, ~~"COM-001",~~ "잘못된 입력입니다"),
UNAUTHORIZED(401, ~~"COM-002",~~ "인증이 필요합니다"),
SERVER_ERROR(500, ~~"COM-003",~~ "서버 오류가 발생했습니다");
private final int status;
private final String code;
private final String message;
}
@RequiredArgsConstructor : 생성자를 직접 구현
public enum GlobalErrorCode implements ErrorCode {
// 공통 에러
INVALID_INPUT_VALUE(400, ~~"GLOBAL-001",~~ "잘못된 입력값입니다"),
INTERNAL_SERVER_ERROR(500, ~~"GLOBAL-002",~~ "서버 오류가 발생했습니다"),
// ... 다른 에러 코드들 ...
private final int status;
private final String code;
private final String message;
// 직접 생성자 구현
GlobalErrorCode(int status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}
// 직접 getter 구현
@Override
public int getStatus() {
return status;
}
@Override
public String code() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
@Getter
@RequiredArgsConstructor
public enum GlobalErrorCode implements ErrorCode {
// ... 에러 코드들 ...
private final int status;
private final String code;
private final String message;
@Override
public String code() {
return name();
//String code 부분에 enum의 name을 넣으면 되므로 파라미터로 400이랑 message만 있으면 된다!
}
}
공통 응답 코드를 만들었다면, 이제는 에러를 던질 수 있는 GlobalException을 만들어야 함.
// 이건 불가능해! - ErrorCode는 예외 클래스가 아니라서
throw GlobalErrorCode.USER_NOT_FOUND; // 컴파일 에러!
// 이건 가능해! - GlobalException은 RuntimeException을 상속받은 예외 클래스니까
throw new GlobalException(GlobalErrorCode.USER_NOT_FOUND); // OK!
비즈니스 로직에서 문제가 생겼을 때 throw new GlobalException(에러코드)
형태로 예외를 발생시키고, 이걸 GlobalExceptionHandler
가 잡아서 처리하는 구조
CommonResponseDto.ok(데이터)
호출하여 성공 응답 객체 생성{
"success": true,
"data": { ... 실제 데이터 ... },
"error": null
}
throw new GlobalException(에러코드)
실행GlobalExceptionHandler
가 예외 캐치ErrorResponse
생성 및 CommonResponseDto.error()
호출{
"success": false,
"data": null,
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "필수 입력값이 누락되었습니다.",
"status": 400
}
}
→ return CommonResponseDto.ok()
라인은 실행되지 않음
@ExceptionHandler
어노테이션을 통해 특정 타입의 예외 발생 시 해당 메서드 자동 호출log.error()
등을 통해)ErrorResponse
객체 생성 - 에러 코드, 메시지, 상태 코드 등 포함CommonResponseDto.error()
호출해 표준화된 응답 형식에 에러 정보 담기ResponseEntity
객체 반환해 클라이언트에게 응답 전송