[데브코스] Spring Boot REST API 실습 (31강) - 글 수정 API 구현 + RsData 설계 보완 (statusCode의 진짜 역할)

zuno·2026년 1월 5일

이번 강의는 기능 하나를 추가하면서
“아, 이래서 이 필드가 존재하는구나”가 명확해지는 강의였다.

핵심은 세 가지다.

  • 글 수정 API(PUT) 구현
  • RsData 생성자 버그 수정
  • statusCode 필드의 존재 이유 이해

📌 현재 RsData 구조

public record RsData<T>(
        String resultCode,
        @JsonIgnore
        int statusCode,
        String msg,
        T data
) {
    public RsData(String resultCode, String msg) {
        this(resultCode, msg, null);
    }

    public RsData(String resultCode, String msg, T data) {
        this(
            resultCode,
            Integer.parseInt(resultCode.split("-", 2)[0]),
            msg,
            data
        );
    }
}

여기서 눈에 띄는 부분이 있다.

@JsonIgnore
int statusCode

JSON 응답으로는 절대 내려가지 않는 값이다.

그런데도 굳이 statusCode를 왜 넣었을까?


🤔 statusCode는 왜 필요한가?

결론부터 말하면,

HTTP 응답 코드를 설정하기 위한 내부 전용 필드다.


🔁 전체 흐름을 다시 보자

1️⃣ 컨트롤러는 RsData를 반환
2️⃣ resultCode 예: "201-1"
3️⃣ RsData 생성자에서:

Integer.parseInt(resultCode.split("-", 2)[0])

201 추출
4️⃣ 이 값이 statusCode에 저장됨
5️⃣ ResponseAspect(AOP)가 이 값을 읽어서
6️⃣ HttpServletResponse.setStatus(statusCode) 실행

📌 즉,

  • resultCode의미 + 규칙
  • statusCodeHTTP 레벨 제어용 내부 데이터

❓ 그럼 JSON으로 안 내려보내는데 굳이 필드가 필요해?

👉 필요하다. 아주 중요하다.

이유는 명확하다.

  • AOP는 메서드 반환값(RsData) 만 보고 판단한다
  • HTTP 응답 코드는 JSON 변환 이전 단계에서 설정해야 한다
  • JSON에 포함되든 말든 상관없이
  • 서버 내부에서 상태 코드를 결정할 데이터가 필요하다

그래서 @JsonIgnore가 붙어 있는 것.

statusCode는
클라이언트를 위한 데이터 ❌
서버의 동작을 위한 데이터 ⭕

🧠 왜 resultCode만 쓰지 않고 statusCode를 분리했을까?

이렇게도 생각할 수 있다.

“resultCode에서 매번 파싱하면 되잖아?”

하지만 분리한 이유는 설계적으로 훨씬 깔끔하기 때문이다.

  • resultCode → 비즈니스 의미 (201-1, 404-1)
  • statusCode → HTTP 레벨 의미 (201, 404)

👉 역할 분리(SRP)
👉 AOP에서 파싱 로직을 없애기 위함


📌 글 수정 API (PUT) 구현

🔹 입력 DTO

record PostModifyReqBody(
        @NotBlank
        @Size(min = 2, max = 100)
        String title,
        @NotBlank
        @Size(min = 2, max = 5000)
        String content
) {}
  • JSON 입력 전용
  • @Valid + @RequestBody 필수

🔹 수정 API

@PutMapping("/{id}")
@Transactional
public RsData<Void> modify(
        @PathVariable int id,
        @Valid @RequestBody PostModifyReqBody reqBody
) {
    Post post = postService.findById(id).get();
    postService.modify(post, reqBody.title, reqBody.content);

    return new RsData<>(
            "200-1",
            "%d번 글이 수정되었습니다.".formatted(post.getId())
    );
}

✔ 성공/실패만 알리므로 RsData<Void>
✔ 수정은 200 OK가 가장 자연스럽다


🐞 RsData 생성자 버그 수정 포인트

이전 구조에서는 특정 생성자 조합에서
msg가 제대로 전달되지 않는 문제가 있었다.

그래서 지금 구조처럼 모든 생성자가 최종 canonical constructor로 모이도록 수정했다.

this(resultCode, statusCode, msg, data);

👉 생성자 체이닝을 명확히 해서
👉 데이터 누락 가능성을 원천 차단


🔒 @JsonIgnore의 의미 다시 정리

@JsonIgnore
int statusCode

이 한 줄의 의미는 이거다.

“이 값은 JSON 응답에는 포함되지 않지만
서버 내부 로직에서는 반드시 필요하다.”

  • 클라이언트: resultCode, msg, data만 보면 됨
  • 서버: statusCode로 HTTP 응답 제어

✍️ 마무리 정리

  • statusCode보내기 위한 값이 아니라 쓰기 위한 값
  • JSON 응답과 HTTP 응답 코드는 완전히 다른 레이어
  • RsData는 단순 DTO가 아니라 응답 정책의 중심
  • @JsonIgnore는 “안 중요해서”가 아니라 “역할이 다르기 때문”

💬 개인적인 느낀 점

이번 강의에서 제일 인상 깊었던 건 이거였다.

“지금은 쓸모 없어 보이는데
나중에 보면 왜 필요한지 알게 되는 필드”

statusCode가 딱 그랬다.

처음 보면
“이거 왜 있지?” 싶은데
AOP까지 흐름을 다 이해하고 나니까

👉 없으면 구조가 무너지는 핵심 필드였다.

점점 코드가
“동작하는 코드”에서
“설계가 보이는 코드”로 바뀌는 느낌이 들었다.

0개의 댓글