간단한 토이 프로젝트 진행 중에 API Response를 만들어주다가 공통으로 만들면 편하지 않을까 해서 이것저것 찾아보다가 신기한게 많아서 공부한 내용을 정리해보려 한다.
일반적으로 클라이언트에서 서버로 요청을 보내면 서버에서 처리한 후 응답을 클라이언트로 다시 보내주어야 한다.
이 경우 String으로 보내는 것이 아니라 ResponseEntity
를 사용해서 응답값을 생성해 반환하는 것이 일반적인 방법이라고 한다.
ResponseEntity
는 스프링 프레임워크에서 제공하는 클래스로, HTTP 응답을 나타내는 객체다. 이 클래스는 웹 애플리케이션에서 컨트롤러 메서드에서 생성된 응답 데이터를 래핑하고, HTTP 응답 헤더 및 상태 코드를 설정할 수 있는 방법을 제공한다.
HTTP 응답 데이터 래핑: HTTP 응답으로 반환할 데이터를 포함한다. 이 데이터는 JSON, HTML, XML 또는 기타 형식일 수 있으며, 제네릭 타입을 사용하여 지정할 수 있다. 예를 들어, ResponseEntity<String>
은 문자열 데이터를 나타낸다.
HTTP 헤더 설정: HTTP 응답 헤더를 설정할 수 있다. HTTPHeaders를 사용하여 커스텀 헤더를 추가하거나 Content-Type
, Content-Disposition
등의 기본 헤더를 설정할 수 있다.
HTTP 상태 코드 지정: HTTP 응답의 상태 코드를 설정하는데 사용된다. 예를 들어 ResponseEntity.ok()
는 HTTP 상태 코드 200 (OK)을 설정한다.
유연성: 스프링 컨트롤러에서 ResponseEntity
를 반환하면, HTTP 응답의 상태 코드, 헤더 및 본문 데이터를 동적으로 조작할 수 있다. 이것은 다양한 상황에 따라 다른 응답을 생성하고 반환하는데 사용된다.
Exception Handling: 예외 처리에 활용할 수 있다. 예외가 발생한 경우, 컨트롤러 메서드에서 responseEntity
를 사용하여 에러 응답을 반환할 수 있다.
@GetMapping("/example")
public ResponseEntity<String> example() {
String data = "Hello, World!";
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "SomeValue");
return new ResponseEntity<>(data, headers, HttpStatus.OK);
}
/example
엔드포인트에 대한 GET 요청에 응답하는 컨트롤러다. ResponseEntity
를 사용하여 응답 데이터, 헤더 및 HTTP 상태 코드가 설정된다.
하나하나 모든 ResponseEntity를 만들기에는 중복되는 코드도 많고 프론트개발을 주로 하다보니 공통 Response를 찾아서 만들어보게 되었다.
응답을 JSON 객체로 보내기 위해 먼저 ResponseData를 만들어주었다.
package com.toy.web.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor // 생성자를 자동으로 만들어 준다
public class ResponseData<T> {
private int statusCode;
private String responseMessage;
}
여기까지는 괜찮았다. statusCode와 responseMessage는 생성자를 통해 할당해주면 된다.
하지만 반환하는 data가 있는 응답도 있고 없는 응답도 있을 것이다. 이 경우에 어떻게 해야하는지 살펴보자.
먼저 공통 HTTP 응답을 만들어 주었다.
package com.toy.web.response;
public class StatusCode {
public static final int OK = 200;
public static final int CREATED = 201;
public static final int BAD_REQUEST = 400;
public static final int NOT_FOUND = 404;
public static final int INTERNAL_SERVER_ERROR = 500;
}
아직 배우는 단계이기 때문에 그렇게 많지 않고 간단하게 몇 개만 작성했다.
package com.toy.web.response;
public class ResponseMessage {
public static final String SIGN_IN_SUCCESS = "로그인 성공.";
public static final String SIGN_IN_FAIL = "로그인 실패.";
public static final String SIGN_UP_SUCCESS = "회원가입 성공.";
public static final String SIGN_UP_FAIL = "회원가입 실패.";
public static final String ALREADY_USER = "이미 존재하는 아이디입니다.";
}
아직 로그인, 회원가입 기능을 만드는 중이라 이번에도 필요한 것만 먼저 작성했다.
참고한 글에서 처음보는 패턴을 사용해서 만들길래 일단 따라 만들고 한줄 한줄 뜯어봐서 이해했다.
package com.toy.web.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor // 생성자를 자동으로 만들어 준다
@Builder
public class ResponseData<T> {
private int statusCode;
private String responseMessage;
private T data;
public ResponseData(final int statusCode, final String responseMessage) {
this.statusCode = statusCode;
this.responseMessage = responseMessage;
this.data = null;
}
public static<T> ResponseData<T> res(final int statusCode, final String responseMessage) {
return res(statusCode, responseMessage, null);
}
public static<T> ResponseData<T> res(final int statusCode, final String responseMessage, final T t) {
return ResponseData.<T>builder()
.data(t)
.statusCode(statusCode)
.responseMessage(responseMessage)
.build();
}
}
Lombok
의 @Builder
어노테이션: 이 어노테이션을 사용하면 객체를 더 쉽게 생성할 수 있다. ResponseData 클래스에 빌더 패턴을 생성하고, 메서드 체이닝을 통해 객체를 초기화할 수 있다.
private T data;
: 응답 데이터를 저장하고 아직 타입이 정해지지 않은 제네릭 타입의 필드다.
빌더 패턴
빌더 패턴은 복잡한 객체의 생성을 단순화하고 가독성을 향상시키기 위한 디자인 패턴 중 하나다.
객체를 구성하는 단계에서 메서드 체이닝을 통해 표현할 수 있다.
선택적인 필드만 설정하고, 나머지는 기본값으로 초기화할 수 있다.
객체의 불변성을 유지할 수 있다. 객체가 생성된 후에 필드를 변경할 수 없다.
이와 같이 빌더 패턴으로 코드를 작성하면 컨트롤러에서 사용할 때 아래와 같이 사용할 수 있다.
ResponseEntity(ResponseData.res(StatusCode.OK, ResponseMessage.SIGN_IN_SUCCESS), HttpStatus.OK);
만약 보내줄 데이터가 없다면 파라미터가 2개인 생성자를 통해서 data를 null로 초기화 해준다.