🍃 Spring

ResponseEntity의 사용법 및 유지보수

loose 2023. 2. 20. 18:59
반응형

ResponseEntity를 쓰는 이유

일반적으로 Controller에서 아래와 같이 객체를 Return 하는 경우 HTTP 응답을 제어할 수가 없습니다.

 

@GetMapping("/")
public User getUser() {
    User user = userService.getUser();
    return user;
}

 

하지만 REST API로 만든다면 클라이언트와 서버 간의 통신에 필요한 정보를 제공해야 합니다.

그럴 때 ResponseEntity를 사용한다면 적절한 상태 코드와 응답 헤더 및 응답 본문을 생성해서 클라이언트에 전달할 수 있습니다.

 

사용 방법은 아래와 같습니다. 성공을 의미하는 OK(200 code)와 함께 user 객체를 Return 하는 코드입니다.

@GetMapping("/")
public ResponseEntity<User> getUser() {
	User user = userService.getUser();
	return ResponseEntity.ok(user);
}

 

ResponseEntity를 잘 쓰는 방법

Return은 생성자보다는 빌더 패턴 사용

생성자 패턴

return new ResponseEntity(body, headers, HttpStatus.valueOf(200));

빌더 패턴

return ResponseEntity.ok()
	.headers(headers)
	.body(body);

ResponseEntity.ok()는 정적 팩토리 메서드입니다.

그리고 뒷부분을 메소드 체이닝으로 연결한 빌더 패턴을 사용하는 것이 의미가 더 직관적이고 유지보수에 좋습니다.

ResponseEntity의 Body 타입을 명시하라

public ResponseEntity getUser() {

위와 같이 메소드를 작성하면 ResponseEntity의 타입을 작성하지 않은 것이라 Object(모든 자바의 부모 클래스)를 Return으로 받습니다.

 

위의 코드는 아래의 코드와 기능적으로 의미가 같습니다.

public ResponseEntity<Object> getUser() {

ResponseEntity의 타입은 명시하지 않으면 Object 타입을 Return 해줍니다.

다만 대부분의 상황에서는 유지보수를 위해 타입을 명시해 주는 것이 직관적이므로 좋습니다.

 

타입을 여러 개 받고 싶다면 Return 타입은 Object 대신 와일드카드 사용

반환 타입이 명확하지 않아도 Return 된다

public ResponseEntity<Object> getUsers() {
    List<User> users = userService.getUsers();
    return ResponseEntity.ok(users);
}
public ResponseEntity<?> getUsers() {
    List<User> users = userService.getUsers();
    return ResponseEntity.ok(users);
}

보통 타입을 여러개 받고 싶은 경우 아래와 같이 Object나 와일드카드를 사용할 수 있습니다.

이러한 방법은 개발 구성원들끼리 공유될 경우 개발 생산성을 높일 수 있기 때문에 좋은 선택이 될 수 있습니다.

둘 모두 사용 가능하지만 Return 할 때 객체의 타입이 명확하지 않을 때는 Object를 사용하는 것보다 와일드카드를 사용하는 것이 좋습니다.

정해지지 않은 타입을 반환한다는 점에서는 Object나 와일드카드나 같은 기능을 하지만 와일드카드를 사용하면 반환할 수 있는 객체의 타입이 명확하지 않아도 사용이 가능합니다.

 

예시로는 아래와 같습니다.

@GetMapping("/users")
public ResponseEntity<?> getUsers() {
    List<?> users = userService.getUsers();
    return ResponseEntity.ok(users);
}

와일드카드, Object vs 사용자 객체

와일드카드나 Object를 이용해서 API를 만든다면 해당 API를 사용하는 사용자는 불필요한 형변환 작업을 해줘야 하는 단점이 존재합니다.

 

아래는 (List<User>)로 불필요한 형변환을 하는 예제입니다.

public ResponseEntity<Object> getUsers() {
    List<User> users = userService.getUsers();
    return ResponseEntity.ok(users);
}
ResponseEntity<Objects> response = restTemplate.getForEntity("/users", Objects.class);
List<User> users = (List<User>) response.getBody();

그래서 와일드카드가 아닌 타입 파라미터를 사용한다면 컴파일 타임에 자동 형변환이 되므로 개발자는 따로 형변환을 해줄 필요가 없습니다.

@GetMapping("/")
public ResponseEntity<T> getUser() {
    T user = userService.getUser();
    return ResponseEntity.ok(user);
}

 

하지만 API 설계 측면에서는 타입 파라미터를 이용하는 것보다 명시적으로 사용자 객체를 지정해 주는 것이 더 좋습니다.

@GetMapping("/users")
public ResponseEntity<User> getUser() {
    User user = userService.getUsers();
    return ResponseEntity.ok(user);
}

 

객체를 명시적으로 쓰는 것이 해당 라이브러리를 사용하는 개발자들한테 더 직관적으로 보이기 때문에 어떻게 사용하는지 더 직관적입니다.

 

하지만 하나의 프로젝트 내에 구성원들끼리만 사용하는 개발이라면 와일드카드를 사용해서 개발하는 것이 생산성이 더 올라갈 수 있습니다.

각각의 방식은 어떤 목적을 가지고 있느냐에 Trade-Off가 존재하기 때문에 각각 장단점이 있습니다.

 

결론적으로 API를 제공하는 형태라면 타입은 항상 명시적으로 작성하는 것이 좋고 개발 구성원들끼리 단일 프로젝트를 설계한다면 와일드카드로 전체 타입을 받는 형태로 개발하는 것이 생산성에 더 이로울 수 있습니다.

물론 단일 프로젝트라 하더라도 규모가 크다면 와일드카드가 좋지 않은 선택이 될 수도 있겠습니다.

 

 

 

 

 

 

 

728x90