본문 바로가기

실시간 채팅 솔루션 개발/문제 해결 사례

유연함을 위해 Custom Result 타입을 사용하자

API 스펙 변경에 유연하게 대응해야 합니다.

컬렉션 그 자체로 반환하지 말고 커스텀 타입으로 한 번 감싸서 반환합시다. 아래 코드로 살펴봅시다.

 

@GetMapping()
public ResponseEntity<List<RoomResponseDTO>> getAllRooms() {
    List<Room> rooms = roomService.getAllRooms();
    List<RoomResponseDTO> collect = rooms.stream()
            .filter(m -> !m.isDeleted())
            .map(m-> RoomResponseDTO.from(m))
            .toList();

    return ResponseEntity.ok().body(collect);
}

 

엔티티 자체를 API Response에 노출하지 않고, Room 엔티티를 RoomResponseDTO로 변환하고 있습니다. 엔티티를 직접 API 스펙에 노출하지 않았기 때문에 엔티티의 변경이 API 스펙의 변경으로 이어지지 않습니다. 또한 프레젠테이션 계층을 위한 로직(@Valid 등)을 엔티티가 아닌 DTO에서 관리할 수 있습니다.

 

하지만 getAllRooms 메서드는 ResponseEntity<List<RoomResponseDTO>>를 반환하며 API 스펙이 고정됩니다. 이처럼 컬렉션을 직접 반환하면 향후 API 스펙을 변경하기 어렵습니다. 만약 getAllRooms 메서드의 API 스펙에 새로운 필드가 추가된다면, 컬렉션만 반환하는 List<RoomResponseDTO>로 대응하기 어렵습니다.


ResponseEntity로 묶어서 반환하긴 하지만 상태코드(Status), 헤더(headers), 응답데이터(ResponseData) 등을 묶는 역할을 하는 ResponseEntity로는 고정된 API 스펙을 유연하게 만들 수 없습니다.

 

별도의 Result 클래스를 생성해서 해결해야 합니다.

@Data
@AllArgsConstructor
public class Result<T> {
    private T data;
}

@RestController
@RequiredArgsConstructor
public class RoomController {

    ...

    @GetMapping("api/v1/...")
    public ResponseEntity<Result> getAllRooms() {
        List<Room> rooms = roomService.getAllRooms();
        List<RoomResponseDTO> collect = rooms.stream()
                .filter(m -> !m.isDeleted())
                .map(m-> RoomResponseDTO.from(m))
                .toList();

        return ResponseEntity.ok().body(new Result(collect));
    }
}

 

컬렉션을 Result 커스텀 타입으로 한 번 감싸서 ResponseData로 보내면 API스펙이 변경되어 count 필드가 추가되었다면 Result 클래스만 수정하면 대응할 수 있습니다.

 

@Data
@AllArgsConstructor
public class Result<T> {
    private T data;
    private int count;
}

 

API 스펙 변경에 유연하게 대응하기 위해서 앞으로 컬렉션을 커스텀 타입으로 한 번 감싸서 반환하도록 합시다.