SpringBoot를 활용해 Rest API를 짤 때 Controller에서 ResponseEntity를 사용해 적절한 데이터를 반환한다.
기존 프로젝트를 진행할 때 나는 아래와 같은 방식으로 진행했다.
- 반환형 → 와일드 카드 사용
- 개발 초기 반환할 값을 설계하는 과정에서 와일드 카드를 하나씩 수정하는 것이 불편하다고 생각했음
- body와 서비스 메서드 분리 (인라인화 하지 않음)List<MemberEnrollInfoResponse> 를 한줄 써서 반환할 타입을 명시하고, activeMembers 를 body에 넣어주는 방식을 사용함.
- 성공한 응답 코드 OK로 통일세션 추가 요청 → 200OK,
- 성공했지만 반환할 값 없음 → 200OK
- 요청이 성공했을때 200번대의 HttpStatus코드를 구분하지 않고 200으로 통일해서 사용함.
@GetMapping("/active-members")
public ResponseEntity<?> getCurrentActiveMembers() {
List<MemberEnrollInfoResponse> activeMembers = adminService.findCurrentActiveMembers();
return ResponseEntity.ok().body(activeMembers);
}
하지만, 위 방법은 2가지 관점에서 좋은 방법은 아니었기에 이를 기반으로 API를 리팩토링해보겠다.
- 협업을 할 때 다른 개발자가 코드를 수정할 때: 코드 자체가 보수적이고 문서의 역할을 해야한다.
- 요청에 대한 응답이 명확한가? : Rest API의 응답이 200일 때와 204일 때 차이 구분하기
1. ResponseEntity<?> 을 통한 와일드 카드 사용
기존 코드
@GetMapping("/active-members")
public ResponseEntity<?> getCurrentActiveMembers() {
List<MemberEnrollInfoResponse> activeMembers = adminService.findCurrentActiveMembers();
return ResponseEntity.ok().body(activeMembers);
}
와일드 카드를 사용하면 모든 객체가 JSON으로 반환 되는 것을 허용하는데, 위 API에서 정상적으로 작동 시List<MemberEnrollInfoResponse> 를 반환해야만 한다.
다른 객체(String이든, 다른 DTO 클래스든,,)가 반환되면 개발 의도에 벗어난대로 코드를 작성하게 된 것이지만 문제 없이 정상적으로 서버가 돌아간다.
의도와 다르게 작성된 오류가 있는 코드지만 서버단에서 오류를 확인할 수 없다.
결국 에러는 프론트단에서 발생하게 될 것인데, 이런 사소한 부분들은 보수적으로 코드를 짜서 미리 컴파일단에서 잘못된 코드를 방지할 수 있다.
와일드 카드 대신, 명시적으로 반환할 클래스 지정하기
빨간줄로 컴파일 단에서 에러가 발생한다.
이렇게 작성하게 되면, 내 실수, 또는 내가 작성한 코드를 다른 개발자가 수정할때 본인의 의도와 다르게 작성하고 있는지를 알 수 있고 변경 또한 변경을 의식해서 할 수 있다.
2. 응답할 데이터가 없는 경우에도 200 OK를 사용함
기존엔 아래와 같이, 반환하는 값이 없어도 200OK를 반환했다.
@PatchMapping("/reject")
public ResponseEntity<?> rejectApplicant(@RequestBody @Valid MemberRejectRequest memberRejectRequest) {
adminService.rejectApplicant(memberRejectRequest);
return ResponseEntity.ok().build();
}
API 요청이 되었을때 요청이 성공했음을 의미하기에 200을 사용했다.
이 건에 대해선 200OK에 body에 아무것도 넣어주지 않으면 같은 역할을 하기 때문에 굳이 리팩토링을 해야할까? 고민이 되었다.
하지만, 2가지를 고려해 Patch, Put, Delete 요청에서 반환형이 Void인 경우 noContent 를 반환하기로 했다.
- 조금 더 구체적이고 상세한 응답을 주는 것이 더 정확한 의미를 전달하는 것 같아
- ok(), body() 에 실수로 다른 값을 넣게 된다면 값이 응답을 하게 된다는 점
@PatchMapping("/reject")
public ResponseEntity<Void> rejectApplicant(@RequestBody @Valid MemberRejectRequest memberRejectRequest) {
adminService.rejectApplicant(memberRejectRequest);
return ResponseEntity.noContent().build();
}
3. Body와 서비스 메서드를 분리함.
@GetMapping("/result/kings")
public ResponseEntity<?> findFinalKingMembers(@RequestParam("educationId") Long educationId) {
List<KingMemberInfo> response = educationService.findKingMemberInfo(educationId);
return ResponseEntity.ok().body(response);
}
인라인화 하지 않고, response라는 변수를 따로 두는 것이 반환형을 볼 수 있고, 한줄에 너무 많은 정보를 담지 않아서 더 읽기 쉬운 코드라는 생각이 들었다.
하지만,,, 와일드 카드를 사용하지 않고 반환형을 명시한다면, 코드 길이가 짧아지고 반환형도 알 수 있으니 이런 방법도 꽤 가독성이 좋은 코드가 아닐까 싶다.
@GetMapping("/result/kings")
public ResponseEntity<List<KingMemberInfo>> findFinalKingMembers(@RequestParam("educationId") Long educationId) {
return ResponseEntity.ok().body(educationService.findKingMemberInfo(educationId));
}
마치며
개발 초기엔 편리함을 추구하지만, 서비스를 운영하고 유지보수하는 과정에서 더 중요한 것은 ‘읽기 쉽고 수정하기 쉬운’ 코드를 개발 초기부터 짜는 것이라는 것을 느낀다. 이 경험을 바탕으로 개발을 할 때 추후에 수정될 부분이 무엇인지 고려하고 이를 수정하기 쉽게 코드를 짜도록 노력해야곘다.
수정된 Pull Request 링크
https://github.com/IT-Cotato/CS-Quiz-BE/pull/148
추가적인 고민
- Rest API에 Http Status Code를 어디까지?
- 토큰 재발급 또한 ‘리소스’를 생성한 것인가?
'프로젝트 > COTATO.KR' 카테고리의 다른 글
멱등성을 통한 정답 제출 API 중복 요청 방지 (이론) (0) | 2024.06.01 |
---|---|
퀴즈 객체 양방향 매핑 없애기 (0) | 2024.04.24 |
여러 객체를 삭제할 때 쿼리 줄이기 (0) | 2024.04.24 |
MethodArgumentNotValidException 처리하기 (0) | 2024.04.17 |
ErrorResponse로 전달할 필드에 대한 고민 (0) | 2024.04.02 |