이번 글은 데브코스 Spring Boot REST API 실습 14강 내용을 정리한 글이다.
14강에서는 본격적으로 공통 응답 객체(RsData) 를 도입하면서,
REST API에서 “어떤 응답에 공통 포맷이 필요한가?” 를 설계 관점에서 다룬다.
14강의 핵심 메시지는 명확하다.
resultCode와 msg는
조회를 제외한 모든 요청에 대한 응답의 필수 요소다.
이를 위해 RsData 클래스를 도입한다.
REST API에서 요청은 크게 두 종류로 나뉜다.
👉 RsData는 이 “행위 요청”을 위한 공통 응답 포맷이다.
조회 API를 제외한 모든 요청
예시:
👉 “무언가를 했다”는 요청에는 결과 코드와 메시지가 필요하다.
조회 API의 성공 응답
조회 요청은:
그래서 다음과 같은 응답은 충분하다.
{
"id": 1,
"createDate": "2025-12-05T14:26:27.394515",
"modifyDate": "2025-12-05T14:26:27.394515",
"title": "제목 1",
"content": "내용 1"
}
물론 조회 API에도 RsData를 적용할 수는 있다.
{
"resultCode": "200-1",
"msg": "조회 성공",
"data": {
"id": 1,
"createDate": "2025-12-05T14:26:27.394515",
"modifyDate": "2025-12-05T14:26:27.394515",
"title": "제목 1",
"content": "내용 1"
}
}
하지만 강사님은 이를 오버스펙(over-spec) 이라고 설명하셨다.
이유는 다음과 같다.
👉 그래서 “조회 성공”에는 RsData가 필요 없다.
중요한 예외가 하나 있다.
조회 요청이라도 실패할 수 있는 경우에는 RsData가 필요하다.
예시:
이 경우에는 다음과 같은 응답이 내려간다.
{
"resultCode": "403-1",
"msg": "접근 권한이 없습니다."
}
즉,
이렇게 역할이 분리된다.
{
"id": 1,
"createDate": "2025-12-05T14:26:27.394515",
"modifyDate": "2025-12-05T14:26:27.394515",
"title": "제목 1",
"content": "내용 1"
}
{
"resultCode": "200-1",
"msg": "조회 성공",
"data": {
"id": 1,
"createDate": "2025-12-05T14:26:27.394515",
"modifyDate": "2025-12-05T14:26:27.394515",
"title": "제목 1",
"content": "내용 1"
}
}
👉 틀린 구조는 아니지만, 굳이 필요하지 않은 구조다.
정리하면 다음과 같다.
이렇게 하면:
14강은 “응답을 어떻게 내려야 좋은 REST API인가”를
설계 관점에서 정리한 강의였다.
14강에서 RsData를 도입하면서,
강의를 따라가다 보니 자연스럽게 몇 가지 궁금증이 생겼다.
강의 흐름과 직접 구현하면서 헷갈렸던 부분들을 정리해본다.
public record RsData(String resultCode, String msg) {
}
처음에는 이런 의문이 들었다.
“getter도 없고,
생성자만 public인데
Jackson이 이걸 어떻게 JSON으로 만들지?”
결론부터 말하면,
record는 컴파일 시점에 accessor 메서드를 자동으로 생성한다.
resultCode()
msg()
Jackson은 JSON 직렬화 시:
그래서 @Getter가 없어도 JSON 응답이 정상적으로 나온다.
여기서 또 이런 궁금증이 들었다.
“생성자도 public이면
JSON 만들 때 참고해야 하는 거 아니야?”
하지만 Jackson은 역할을 명확히 나눈다.
직렬화 (객체 → JSON)
→ 이미 만들어진 객체의 값을 읽기만 한다
→ 생성자 ❌ 사용 안 함
역직렬화 (JSON → 객체)
→ 객체를 새로 만들어야 함
→ 생성자 ⭕ 사용
즉, 생성자는
JSON을 출력할 때가 아니라, JSON으로부터 객체를 만들 때만 쓰인다.
댓글 삭제 API를 구현하면서 이런 코드가 있었다.
post.getComments().remove(postComment);
return new RsData(
"200-1",
"댓글이 삭제되었습니다."
);
처음에는 이런 생각이 들었다.
“remove 했는데
postComment 객체는 사라진 거 아닌가?”
하지만 실제로는 그렇지 않다.
remove()는 리스트에서 참조만 제거postComment 변수는 여전히 객체를 참조 중즉,
리스트에서 제거 = 객체 삭제 ❌
JPA 관점에서도:
그래서 삭제 직후에도:
postComment.getId()
같은 값 접근이 가능한 것이다.
정리해보면 지금 구조는 다음 흐름으로 이해할 수 있다.
remove)RsData record로 결과 전달이 흐름 덕분에:
강의를 따라가다 보니
“왜 이렇게 설계했을까?”라는 궁금증이 자연스럽게 생겼고,
그 질문을 하나씩 풀다 보니
이 한 번에 연결되기 시작했다.
단순히 기능을 구현하는 것보다,
왜 이런 형태의 코드가 나왔는지를 이해하는 게 더 중요하다는 걸 느낀 강의였다.