리소스 URI 결정

리소스 URI 결정이란 새로 생성되는 리소스의 고유 식별 주소(URI)를 누가 정하는가의 차이를 의미합니다.

핵심 개념

리소스 URI = 해당 리소스에 접근할 수 있는 고유 주소

예시:
/members/1   → 1번 회원의 URI
/members/2   → 2번 회원의 URI
/orders/100  → 100번 주문의 URI

이 URI를 "서버가 정하느냐" vs "클라이언트가 정하느냐"의 차이

POST — 서버가 URI를 결정

// 클라이언트: "회원 하나 만들어줘" (URI를 지정하지 않음)
POST /members HTTP/1.1
Content-Type: application/json

{"name": "홍길동", "email": "hong@test.com"}

// 서버: "알겠어, id=1로 만들었어" (서버가 URI 결정)
HTTP/1.1 201 Created
Location: /members/1    ← 서버가 /members/1이라는 URI를 부여

// 클라이언트는 생성 전에 이 리소스의 URI를 알 수 없음
// 서버의 응답(Location)을 받아야 비로소 알 수 있음
@PostMapping("/members")  // 컬렉션 URI에 요청
public ResponseEntity<MemberResponse> createMember(
        @RequestBody MemberRequest request) {

    // 서버가 id를 자동 생성 (DB auto_increment 등)
    Member member = Member.builder()
            .name(request.getName())
            .email(request.getEmail())
            .build();

    Member saved = memberRepository.save(member);  // id=1 자동 부여

    URI location = URI.create("/members/" + saved.getId());
    return ResponseEntity.created(location).body(new MemberResponse(saved));
}
[POST의 흐름]
클라이언트: POST /members ← 컬렉션(목록) URI에 요청
서버: id=1 생성 → /members/1 탄생
서버: id=2 생성 → /members/2 탄생
서버: id=3 생성 → /members/3 탄생

→ 클라이언트는 "만들어달라"만 요청
→ 어떤 URI가 될지는 서버가 결정

PUT — 클라이언트가 URI를 결정

// 클라이언트: "/members/1에 이 데이터를 넣어줘" (URI를 직접 지정)
PUT /members/1 HTTP/1.1
Content-Type: application/json

{"name": "홍길동", "email": "hong@test.com"}

// 서버: "/members/1이 없으니 만들었어" 또는 "있으니 교체했어"
HTTP/1.1 201 Created
// 또는
HTTP/1.1 200 OK

// 클라이언트가 생성 전에 이미 URI(/members/1)를 알고 있음
@PutMapping("/members/{id}")  // 특정 리소스 URI에 요청
public ResponseEntity<MemberResponse> createOrUpdate(
        @PathVariable Long id,    // 클라이언트가 id를 지정
        @RequestBody MemberRequest request) {

    boolean exists = memberRepository.existsById(id);

    // 클라이언트가 지정한 id로 생성 또는 교체
    Member member = Member.builder()
            .id(id)  // 클라이언트가 결정한 id 사용
            .name(request.getName())
            .email(request.getEmail())
            .build();

    memberRepository.save(member);

    if (exists) {
        return ResponseEntity.ok(new MemberResponse(member));
    } else {
        URI location = URI.create("/members/" + id);
        return ResponseEntity.created(location).body(new MemberResponse(member));
    }
}
[PUT의 흐름]
클라이언트: PUT /members/1 ← "이 URI에 넣어줘"
서버: /members/1 없음 → 생성 or 있음 → 교체

클라이언트: PUT /members/99 ← "이 URI에 넣어줘"
서버: /members/99 없음 → 생성 or 있음 → 교체

→ 클라이언트가 URI를 직접 지정
→ 서버는 해당 URI에 리소스를 생성하거나 교체

비유로 이해하기

[POST — 식당 번호표]
손님: "자리 하나 주세요" (번호를 지정하지 않음)
직원: "3번 테이블로 안내해드릴게요" (직원이 번호 결정)
→ 손님은 몇 번 테이블인지 미리 알 수 없음

[PUT — 예약석]
손님: "7번 테이블에 앉을게요" (번호를 직접 지정)
직원: "네, 7번 테이블 준비해드리겠습니다" (손님이 번호 결정)
→ 손님이 원하는 테이블을 직접 선택

실무에서의 차이

[POST가 적합한 경우 — 대부분의 생성 작업]
회원가입:    POST /members       → 서버가 회원번호 부여
주문:       POST /orders        → 서버가 주문번호 부여
게시글 작성: POST /posts         → 서버가 게시글 ID 부여

→ 클라이언트가 ID를 알 필요 없음
→ 서버가 DB auto_increment 등으로 자동 생성

[PUT이 적합한 경우 — 클라이언트가 식별자를 아는 경우]
파일 업로드:      PUT /files/report-2025.pdf   → 파일명을 클라이언트가 지정
설정 저장:        PUT /settings/notification   → 설정 키를 클라이언트가 알고 있음
외부 ID 연동:     PUT /users/github-12345      → 외부 시스템의 ID를 그대로 사용

요청 대상 URI의 의미 차이

항목POSTPUT
요청 URI/members (컬렉션)/members/1 (개별 리소스)
URI의 의미"이 컬렉션에 추가해줘""이 URI의 리소스를 이 데이터로 설정해줘"
서버의 역할새 URI를 생성하여 부여지정된 URI에 리소스를 배치
POST /members     → "members 컬렉션에 새 멤버를 추가해줘"
                    서버: "알겠어, /members/1로 만들었어"

PUT /members/1    → "/members/1을 이 데이터로 설정해줘"
                    서버: "알겠어, /members/1을 설정했어"

면접 포인트

CSLOG에서 보기