[데브코스] Spring Boot REST API 실습 (14강) - resultCode와 msg는 조회를 제외한 모든 요청 응답의 필수 요소 — RsData 도입

zuno·2025년 12월 21일

이번 글은 데브코스 Spring Boot REST API 실습 14강 내용을 정리한 글이다.
14강에서는 본격적으로 공통 응답 객체(RsData) 를 도입하면서,
REST API에서 “어떤 응답에 공통 포맷이 필요한가?” 를 설계 관점에서 다룬다.


1️⃣ 14강의 핵심 메시지

14강의 핵심 메시지는 명확하다.

resultCode와 msg는
조회를 제외한 모든 요청에 대한 응답의 필수 요소다.

이를 위해 RsData 클래스를 도입한다.


2️⃣ 왜 RsData가 필요한가?

REST API에서 요청은 크게 두 종류로 나뉜다.

① 조회 요청 (Read)

  • 데이터를 요청
  • 성공 시 → 데이터 자체가 응답
  • 실패 가능성이 상대적으로 낮음

② 행위 요청 (Create / Update / Delete / Action)

  • 무언가를 “시킨다”
  • 성공/실패 여부가 중요
  • 결과에 대한 메시지가 필요

👉 RsData는 이 “행위 요청”을 위한 공통 응답 포맷이다.


3️⃣ RsData의 사용 원칙

✔ RsData를 사용하는 경우

조회 API를 제외한 모든 요청

예시:

  • 글 작성 API 요청에 대한 응답
  • 글 수정 API 요청에 대한 응답
  • 글 삭제 API 요청에 대한 응답
  • 댓글 작성 API 요청에 대한 응답
  • 댓글 수정 API 요청에 대한 응답
  • 댓글 삭제 API 요청에 대한 응답
  • 장바구니에 아이템 담기 요청에 대한 응답
  • 장바구니에서 아이템 삭제 요청에 대한 응답
  • 장바구니 아이템으로 주문 생성 요청에 대한 응답
  • 주문 취소 요청에 대한 응답
  • 주문 결제 처리 요청에 대한 응답

👉 “무언가를 했다”는 요청에는 결과 코드와 메시지가 필요하다.


❌ RsData를 사용하지 않는 경우

조회 API의 성공 응답

조회 요청은:

  • 성공하면 데이터가 곧 응답
  • 실패할 확률이 낮음
  • 굳이 resultCode/msg로 감쌀 필요가 없음

그래서 다음과 같은 응답은 충분하다.

{
  "id": 1,
  "createDate": "2025-12-05T14:26:27.394515",
  "modifyDate": "2025-12-05T14:26:27.394515",
  "title": "제목 1",
  "content": "내용 1"
}

4️⃣ 조회에 RsData를 적용하면 왜 오버스펙일까?

물론 조회 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) 이라고 설명하셨다.

이유는 다음과 같다.

  • 조회 성공 시 이미 “데이터 존재 = 성공”이 명확
  • 매번 resultCode/msg를 감싸면 응답 구조가 불필요하게 복잡
  • 프론트에서도 data를 한 단계 더 파고들어야 함

👉 그래서 “조회 성공”에는 RsData가 필요 없다.


5️⃣ 그럼 조회 실패는 어떻게 할까?

중요한 예외가 하나 있다.

조회 요청이라도 실패할 수 있는 경우에는 RsData가 필요하다.

예시:

  • 권한 부족
  • 접근 불가
  • 존재하지 않는 리소스

이 경우에는 다음과 같은 응답이 내려간다.

{
  "resultCode": "403-1",
  "msg": "접근 권한이 없습니다."
}

즉,

  • 조회 성공 → 데이터 그대로
  • 조회 실패 → RsData 기반 오류 응답

이렇게 역할이 분리된다.


6️⃣ RsData 적용 전 / 후 비교

RsData 적용 전 (조회 성공)

{
  "id": 1,
  "createDate": "2025-12-05T14:26:27.394515",
  "modifyDate": "2025-12-05T14:26:27.394515",
  "title": "제목 1",
  "content": "내용 1"
}

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"
  }
}

👉 틀린 구조는 아니지만, 굳이 필요하지 않은 구조다.


7️⃣ 14강에서 정리된 응답 설계 원칙

정리하면 다음과 같다.

  • 조회 성공 응답
    데이터 그대로 반환
  • 조회 실패 응답
    RsData 기반 오류 응답
  • 생성/수정/삭제/행위 요청
    RsData 기반 성공/실패 응답

이렇게 하면:

  • 응답 구조가 명확해지고
  • 프론트엔드와의 협업이 쉬워지며
  • API의 의도가 분명해진다.

🔚 14강 정리

  • resultCode와 msg는 “행위 요청”의 필수 응답 요소다
  • RsData는 조회를 제외한 모든 요청 응답에 사용된다
  • 조회 성공 응답에 RsData를 쓰는 것은 오버스펙이다
  • 조회 실패 시에는 RsData 기반 오류 응답이 필요하다
  • 응답 설계는 기능보다 협업과 명확성이 더 중요하다

💡 한 줄 요약

14강은 “응답을 어떻게 내려야 좋은 REST API인가”를
설계 관점에서 정리한 강의였다.


🔍 덧붙여서 궁금해져서 정리해본 내용

14강에서 RsData를 도입하면서,
강의를 따라가다 보니 자연스럽게 몇 가지 궁금증이 생겼다.
강의 흐름과 직접 구현하면서 헷갈렸던 부분들을 정리해본다.


1️⃣ record인데 Getter도 없고 생성자만 있는데 JSON이 왜 나올까?

public record RsData(String resultCode, String msg) {
}

처음에는 이런 의문이 들었다.

“getter도 없고,
생성자만 public인데
Jackson이 이걸 어떻게 JSON으로 만들지?”

결론부터 말하면,
record는 컴파일 시점에 accessor 메서드를 자동으로 생성한다.

resultCode()
msg()

Jackson은 JSON 직렬화 시:

  • 생성자를 참고하지 않고
  • 객체의 현재 상태를 읽기 전용(accessor) 으로 꺼내서
  • JSON을 생성한다.

그래서 @Getter가 없어도 JSON 응답이 정상적으로 나온다.


2️⃣ 생성자도 public인데, 왜 JSON 만들 때는 안 쓰일까?

여기서 또 이런 궁금증이 들었다.

“생성자도 public이면
JSON 만들 때 참고해야 하는 거 아니야?”

하지만 Jackson은 역할을 명확히 나눈다.

  • 직렬화 (객체 → JSON)
    → 이미 만들어진 객체의 값을 읽기만 한다
    → 생성자 ❌ 사용 안 함

  • 역직렬화 (JSON → 객체)
    → 객체를 새로 만들어야 함
    → 생성자 ⭕ 사용

즉, 생성자는
JSON을 출력할 때가 아니라, JSON으로부터 객체를 만들 때만 쓰인다.


3️⃣ 댓글 삭제 후에도 id를 어떻게 가져올 수 있을까?

댓글 삭제 API를 구현하면서 이런 코드가 있었다.

post.getComments().remove(postComment);

return new RsData(
    "200-1",
    "댓글이 삭제되었습니다."
);

처음에는 이런 생각이 들었다.

“remove 했는데
postComment 객체는 사라진 거 아닌가?”

하지만 실제로는 그렇지 않다.

  • remove()리스트에서 참조만 제거
  • postComment 변수는 여전히 객체를 참조 중
  • 객체는 메모리에 그대로 존재

즉,

리스트에서 제거 = 객체 삭제 ❌

JPA 관점에서도:

  • 트랜잭션 종료 전까지는 “삭제 예정 상태”
  • 실제 DB DELETE는 커밋 시점에 실행

그래서 삭제 직후에도:

postComment.getId()

같은 값 접근이 가능한 것이다.


4️⃣ 그래서 지금 구조가 자연스럽다

정리해보면 지금 구조는 다음 흐름으로 이해할 수 있다.

  1. 댓글 엔티티 조회 (영속 상태)
  2. 컬렉션에서 제거 (remove)
  3. 트랜잭션 안에서 응답 메시지 생성
  4. RsData record로 결과 전달
  5. 트랜잭션 종료 시 실제 DB DELETE

이 흐름 덕분에:

  • 응답 메시지를 안정적으로 만들 수 있고
  • 삭제 결과를 프론트에 명확히 전달할 수 있다.

💡 정리하며 느낀 점

강의를 따라가다 보니
“왜 이렇게 설계했을까?”라는 궁금증이 자연스럽게 생겼고,
그 질문을 하나씩 풀다 보니

  • record의 역할
  • Jackson 직렬화 방식
  • JPA의 삭제 흐름

이 한 번에 연결되기 시작했다.

단순히 기능을 구현하는 것보다,
왜 이런 형태의 코드가 나왔는지를 이해하는 게 더 중요하다는 걸 느낀 강의였다.

0개의 댓글