ResponseDto 개선하기

2025. 8. 19. 20:14·develop

새로운 프로젝트를 시작할 때 개발 속도를 올리기 위해서 boiler-plate 프로젝트를 시작했다.

나아가 더 나은 구조 설계를 위해 boiler-plate의 각 기능 별 업데이트를 주기적으로 해주려고 한다.

첫번째 boiler-plate의 기능은 공용으로 사용할 ResponseDto를 제작하는 것이다.

 

 

기존 사용하던 ResponseDto

@Getter
public class ResponseDto<T> {

    private final boolean success;
    private final String statusName;
    private final String message;
    private final T response;

    @Builder
    private ResponseDto(boolean success, String statusName, String message, T response) {
        this.success = success;
        this.statusName = statusName;
        this.message = message;
        this.response = response;
    }

    public static <T> ResponseDto<T> success(HttpStatus status, String message, T data) {
        return ResponseDto.<T>builder()
            .success(true)
            .statusName(status.name())
            .message(message)
            .response(data)
            .build();
    }

    public static <T> ResponseDto<T> fail(HttpStatus status, String message) {
        return ResponseDto.<T>builder()
            .success(false)
            .statusName(status.name())
            .message(message)
            .response(null)
            .build();
    }
}



 

기업에서는 ResponseDto를 어떻게 사용하는가

먼저, 나의 ResponseDto의 필드가 최선이 였을까? 라는 생각이 들었다. 물론 확신도 없었다.
따라서, ResponseDto를 사용하는 실제 기업에서는 어떤 필드로 관리하고 있는지 확인할 필요가 있었다.
개발자 도구로 실제 fetch 요청을 확인하는거 만큼 좋은 레퍼런스는 없다고 생각한다.

원티드, 크림, 인프런 등의 여러 기업의 응답 바디를 확인하였다.
필드는 대부분 단조로웠다. data 하나 있는 정도였다.
하지만, 인프런은 기존 나의 필드와 비슷한 구조를 가지고 있었다.

해당 예시 요청에서 message의 값은 ""로 응답이 되었다.
message 필드의 경우에는 디테일한 설명이 필요한 경우에 사용되는 용도라고 판단되었다.
개인적으로는 message 내용 없이 HTTP 메서드를 통해 클라이언트를 이해를 시키는 것이 Restful한 설계라고 생각이들었다.
따라서, message는 디테일한 설명 값이라고 간주하며 설계하려고 한다.

 

인프런을 레펀런스 삼아서 개선을 진행하고자 마음을 먹었으니, 일부로 홈페이지에 간단하게 에러를 내어 에러 응답에 대해서도 확인했다.

 

 

개선 -1 필드 값 수정

개선 전

@Getter
public class ResponseDto<T> {

    private final boolean success;
    private final String statusName;
    private final String message;
    private final T response;
    // 생략

 

개선 후

@Getter
public class ResponseDto<T> {

    private final String statusCode;
    private final String message;
    private final String errorCode;
    private final T data;
// 생략

인프런의 필드를 적극 참고하였다. 사실 팀내에서 편하게 필드를 확립하면 되겠지만, 나는 boiler-plate를 제작하기 때문에 나름의 기준이 필요했다.

 

 

개선 -2 builder private 설정

기존 사용하던 ResponseDto의 생성자의 경우에 builder 어노테이션을 사용하였는데, 별다른 설정을 하지 않았다.
외부에서 ResponseDto의 빌더를 선언할 수 있다는 문제점을 뒤늦게 발견하였다.
따라서, builder 어노테이션에 private 옵션을 주었다.

 

개선 전

    @Builder
    private ResponseDto(boolean success, String statusName, String message, T response) {
        this.success = success;
        this.statusName = statusName;
        this.message = message;
        this.response = response;
    }

 

개선 후

@Builder(access = AccessLevel.PRIVATE)
    private ResponseDto(String statusCode, String message, String errorCode, T data) {
        this.statusCode = statusCode;
        this.message = message;
        this.errorCode = errorCode;
        this.data = data;
    }

 

 

개선 -3 오버로딩 사용

message 필드를 통해 디테일한 설명을 해야되는 경우와 아닌 경우 총 2가지의 경우를 대비해야했다.
객체를 생성하기 위한 팩토리 메서드 내에서 조건을 통해 필드를 세팅해도 되지만, 다형성을 이용한 유연한 처리를 사용하고자 한다.

 

개선 전

    public static <T> ResponseDto<T> success(HttpStatus status, String message, T data) {
        return ResponseDto.<T>builder()
            .success(true)
            .statusName(status.name())
            .message(message)
            .response(data)
            .build();
    }

 

개선 후

public static <T> ResponseDto<T> ofSuccess(HttpStatus status, T data) {
        return ResponseDto.<T>builder()
            .statusCode(status.name())
            .message(null)
            .errorCode(null)
            .data(data)
            .build();
    }

    public static <T> ResponseDto<T> ofSuccess(HttpStatus status, String message, T data) {
        return ResponseDto.<T>builder()
            .statusCode(status.name())
            .message(message)
            .errorCode(null)
            .data(data)
            .build();
    }

 

 

개선 -4 에러 응답 개선

참고로 해당 프로젝트의 ErrorCode는 다음과 같은 이눔으로 정의 되어있다.

@Getter
@AllArgsConstructor
public enum ErrorCode {

    /**
     * 400 BAD_REQUEST : 잘못된 요청
     */
    INVALID_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청 데이터"),

    /**
     * 401 UNAUTHORIZED : 인증 되지 않은 사용자
     */
    AUTHENTICATION_REQUIRED(HttpStatus.UNAUTHORIZED, "인증 필요"),
    INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "권한 정보가 없는 토큰"),
    EXPIRED_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "인증 토큰이 만료"),

    /**
     * 403 FORBIDDEN : 권한 없음
     */
    ACCESS_DENIED(HttpStatus.FORBIDDEN, "접근 권한 없음"),

    /**
     * 404 NOT_FOUND : Resource 를 찾을 수 없음
     */
    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자 없음"),
    RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 리소스 없음"),

    /**
     * 409 : CONFLICT : Resource 의 현재 상태와 충돌
     */
    DUPLICATE_LOGINID(HttpStatus.CONFLICT, "아이디 중복"),
    DUPLICATE_EMAIL(HttpStatus.CONFLICT, "이메일 중복"),
    DUPLICATE_RESOURCE(HttpStatus.CONFLICT, "중복 데이터"),
    RESOURCE_CONFLICT(HttpStatus.CONFLICT, "리소스 상태 충돌"),
    OVER_VALIDATION(HttpStatus.CONFLICT, "저장 한도 초과"),

    /**
     * 500 INTERNAL_SERVER_ERROR : 서버 오류
     */
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류"),
    EXTERNAL_API_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "외부 API 호출 실패");

    private final HttpStatus httpStatus;
    private final String message;
}

HttpStatus, message 등 넘기지 않고 ErrorCode만 파라미터로 넘겨도 객체가 생성되도록 팩토리 메서드를 수정할 필요를 느꼇다.

 

개선 전

public static <T> ResponseDto<T> fail(HttpStatus status, String message) {
        return ResponseDto.<T>builder()
            .success(false)
            .statusName(status.name())
            .message(message)
            .response(null)
            .build();
    }

 

개선 후

public static <T> ResponseDto<T> fromErrorCode(ErrorCode errorCode) {
        return ResponseDto.<T>builder()
            .statusCode(errorCode.getHttpStatus().name())
            .message(errorCode.getMessage())
            .errorCode(errorCode.name())
            .data(null)
            .build();
    }

 

 

개선 -5 ResponseDto 테스트 코드 작성

여러 프로젝트를 진행하면서 ResponseDto에 대한 테스트 코드를 한번도 작성하지 않았다.
나름의 반성을 하면서 간단하게 테스트 코드를 작성하고자 했다.

해당 프로젝트의 테스트 코드 네이밍 컨벤션은 should + 동작 + 조건 을 사용하였다.

 

개선 전

 

"테스트 코드 없음"

 

개선 후

class ResponseDtoTest {

    @Test
    @DisplayName("상태, 데이터가 주어졌을 때 성공 응답을 만듭니다")
    void shouldCreateSuccessResponseWhenStatusAndDataGiven() {
        //given
        HttpStatus status = HttpStatus.OK;
        String data = "testData";

        //when
        ResponseDto<String> responseDto = ResponseDto.ofSuccess(status, data);

        //then
        assertAll(
            () -> assertThat(responseDto.getStatusCode()).isEqualTo(status.name()),
            () -> assertThat(responseDto.getMessage()).isNull(),
            () -> assertThat(responseDto.getErrorCode()).isNull(),
            () -> assertThat(responseDto.getData()).isEqualTo(data)
        );
    }

    @Test
    @DisplayName("상태, 데이터, 메세지가 주어졌을 때 메세지를 포함한 성공 응답을 만듭니다")
    void shouldCreateSuccessResponseWithMessageWhenStatusAndDataAndMessageGiven() {
        //given
        HttpStatus status = HttpStatus.OK;
        String message = "testMessage";
        String data = "testData";

        //when
        ResponseDto<String> responseDto = ResponseDto.ofSuccess(status, message, data);

        //then
        assertAll(
            () -> assertThat(responseDto.getStatusCode()).isEqualTo(status.name()),
            () -> assertThat(responseDto.getMessage()).isEqualTo(message),
            () -> assertThat(responseDto.getErrorCode()).isNull(),
            () -> assertThat(responseDto.getData()).isEqualTo(data)
        );
    }

    @Test
    @DisplayName("에러 코드가 주어졌을 때 에러 응답을 생성합니다")
    void shouldCreateErrorResponseWhenErrorCodeGiven() {
        //given
        ErrorCode internalServerError = ErrorCode.INTERNAL_SERVER_ERROR;

        //when
        ResponseDto<String> responseDto = ResponseDto.fromErrorCode(internalServerError);

        //then
        assertAll(
            () -> assertThat(responseDto.getStatusCode()).isEqualTo(internalServerError.getHttpStatus().name()),
            () -> assertThat(responseDto.getMessage()).isEqualTo(internalServerError.getMessage()),
            () -> assertThat(responseDto.getErrorCode()).isEqualTo(internalServerError.name()),
            () -> assertThat(responseDto.getData()).isNull()
        );
    }
}

 

 

미치며

최종 코드

@Getter
public class ResponseDto<T> {

    private final String statusCode;
    private final String message;
    private final String errorCode;
    private final T data;

    @Builder(access = AccessLevel.PRIVATE)
    private ResponseDto(String statusCode, String message, String errorCode, T data) {
       this.statusCode = statusCode;
       this.message = message;
       this.errorCode = errorCode;
       this.data = data;
    }

    public static <T> ResponseDto<T> ofSuccess(HttpStatus status, T data) {
       return ResponseDto.<T>builder()
          .statusCode(status.name())
          .message(null)
          .errorCode(null)
          .data(data)
          .build();
    }

    public static <T> ResponseDto<T> ofSuccess(HttpStatus status, String message, T data) {
       return ResponseDto.<T>builder()
          .statusCode(status.name())
          .message(message)
          .errorCode(null)
          .data(data)
          .build();
    }

    public static <T> ResponseDto<T> fromErrorCode(ErrorCode errorCode) {
       return ResponseDto.<T>builder()
          .statusCode(errorCode.getHttpStatus().name())
          .message(errorCode.getMessage())
          .errorCode(errorCode.name())
          .data(null)
          .build();
    }
}

 

인프런을 참고해서 ResponseDto 개선했다. 더 좋은 구조와 설계를 발견하거나 생각이 난다면 꾸준히 업데이트 해야겠다.

'develop' 카테고리의 다른 글

WebRTC - EC2 Coturn 서버 구축  (0) 2025.04.03
네이버 로그인 검수 승인받는 법  (0) 2025.04.03
Spring Security 권한 여러 개 (학생/강사 테이블 분리된 인증 서비스 구현)  (0) 2025.01.27
새로운 팀원들 / 코드 리뷰, 테스트 코드를 경험하다.  (1) 2024.12.24
Spring Batch 정리  (2) 2024.10.01
'develop' 카테고리의 다른 글
  • WebRTC - EC2 Coturn 서버 구축
  • 네이버 로그인 검수 승인받는 법
  • Spring Security 권한 여러 개 (학생/강사 테이블 분리된 인증 서비스 구현)
  • 새로운 팀원들 / 코드 리뷰, 테스트 코드를 경험하다.
VANEL
VANEL
break;
  • VANEL
    VANEL의 블로그
    VANEL
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 오류
      • develop
      • 과거 기록
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 이전 블로그
  • 공지사항

  • 인기 글

  • 태그

    spring security
    restdocs
    Spring boot
    코드리뷰
    WebRTC
    Spring
    테스트코드
    coturn
    JWT
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
VANEL
ResponseDto 개선하기
상단으로

티스토리툴바