Response Value
리팩토링 전
@PostMapping("/member")
public String addMember(@Validated @RequestBody SaveMemberRequest request) {
memberService.saveMember(request);
return "ok";
}
@GetMapping("/members")
public MemberInfoResponse getMemberList(){
return memberService.getMembers();
}
Refactoring 후 코드
@PostMapping("/member")
public ResponseData addMember(@Validated @RequestBody SaveMemberRequest request) {
return SingleResponseData.of(memberService.saveMember(request));
}
@GetMapping("/members")
public ResponseData getMemberList(){
return ListResponseData.of(memberService.getMembers());
}
Controller Response 값 refactoring
리팩토링 전에 두가지 기준을 잡고 갔다 - 정상 반환일 경우, @RestControllerAdvice를 통해 오류메세지를 반환하는 경우
정상 반환
{
"statusCode":0,
"message":"string",
"data":"object"
}
정상 반환 일 경우에는 두가지로 또 나뉘었다
- 리스트 형식으로 반환이 되는 경우
- 단일 값으로 반환이 되는 경우이다
각각 data에 두가지 형식으로 나누어 들어가게 만들기 위해 ResponseData라는 공통된 속성과 상속을 받아 사용되는 ListResponseData, SingleResponseData객체 두개를 추가로 만들게 되었고 공통된 값을 프론트로 전달을 할 수 있게 되었다
public abstract class ResponseData {
private int statusCode;
private String message;
protected ResponseData() {
this.statusCode = HttpStatus.OK.value();
this.message = "success";
}
}
//-------------------------------------------------------------//
public class ListResponseData<T> extends ResponseData {
private List<T> data;
private ListResponseData(List<T> data) {
this.data = data;
}
public static <T> ListResponseData of(List<T> data) {
return new ListResponseData(data);
}
}
//-------------------------------------------------------------//
public class SingleResponseData<T> extends ResponseData {
private T data;
private SingleResponseData(T data) {
this.data = data;
}
public static <T> SingleResponseData of(T data) {
return new SingleResponseData(data);
}
}
에러 반환
{
"statusCode":0,
"message":"string",
"error":"object"
}
에러를 반환하는 것도 형식은 비슷하게 만들었지만 message를 enum을 통해서 관리를 하도록 했다
원래는 customException을 만들어서 직접 메세지를 전달하도록 만들었지만 이것조차 하나로 관리를 하도록 하고싶어 변경을 해봤다
enum을 통해서 관리를 한다는 것을 생각하기에는 많은 고민이 있었다
처음에는 상수값을 가진 클래스를 통해서 관리를 하고자 생각을 했다 하지만 이렇게 할때는 statusCode와 message를 동시에 관리를 할 수 없었다
다행이도 다른 좋은 글을 통해 도움을 받을 수 있었다
public enum ErrorCode {
MEMBER_NOT_FOUND(400, "Member Not Found"),
;
private int status;
private final String message;
ErrorCode(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
}
public class ResponseError {
private int statusCode;
private String message;
private List<FieldErrorResponse> error;
private ResponseError(ErrorCode errorCode, List<FieldErrorResponse> error) {
this.statusCode = errorCode.getStatus();
this.message = errorCode.getMessage();
this.error = error;
}
private ResponseError(ErrorCode errorCode) {
this.statusCode = errorCode.getStatus();
this.message = errorCode.getMessage();
this.error = new ArrayList<>();
}
public static ResponseError of(ErrorCode errorCode, BindingResult bindingResult) {
return new ResponseError(errorCode, FieldErrorResponse.of(bindingResult));
}
public static ResponseError of(ErrorCode errorCode) {
return new ResponseError(errorCode);
}
}
public class FieldErrorResponse {
private String field;
private String value;
private String message;
private FieldErrorResponse(FieldError fieldError) {
this.field = fieldError.getField();
this.value = fieldError.getRejectedValue().toString();
this.message = fieldError.getDefaultMessage();
}
public static List<FieldErrorResponse> of(BindingResult bindingResult) {
return bindingResult.getFieldErrors().stream()
.map(FieldErrorResponse::new)
.collect(toList());
}
}
그리고 이러한 enum값을 받을 수 있도록 커스텀 예외를 만들어 관리를 했고 아래와 같이 ErrorCode라는 공통화된 속성으로 메세지와 상태값을 관리를 하니 추후 변경에도 용이할 것이라 생각한다
public class BusinessException extends RuntimeException{
private ErrorCode errorCode;
public BusinessException(String message, ErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
public class EntityNotFoundException extends BusinessException {
public EntityNotFoundException(ErrorCode errorCode){
super(errorCode);
}
}
BusinessException
에서 RuntimeException
을 상속받은 이유는 컨트롤러에서는 처리를 하지않고 RestControllerAdvice에 넘겨 처리를 할것이기 때문에 따로 추가적인 throws Exception
과 같이 method단에 명시를 하고싶지 않기 때문이다
또한 BusinessException
은 RestControllerAdvice
에서 한번에 관리를 하기 위해서 상위 Exception
을 만들게 되었다
private Member findMember(String id) {
return memberRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.MEMBER_NOT_FOUND));
}
아래는 RestControllerAdvice에서 사용되는 예이다
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ExceptionHandler(BusinessException.class)
public ResponseError BusinessExceptionHandler(BusinessException e) {
return ResponseError.of(e.getErrorCode());
}
참고
'공부기록 > Spring' 카테고리의 다른 글
Spring-Rest Docs(3) (0) | 2022.08.16 |
---|---|
Spring-Rest Docs(2) (0) | 2022.08.11 |
Spring-Rest Docs(1) (0) | 2022.08.09 |
Spring boot CORS 설정 (0) | 2022.08.03 |
DTO관련 고민 (0) | 2022.07.13 |