REST API 설계: 자원, 메서드, 무상태, 긴 작업 처리
들어가며
새로 받은 API 문서에 POST /getUser 가 있고, 실패해도 상태 코드는 200 이고 본문에만 에러가 담겨 있는 경우가 있습니다. JSON 을 HTTP 로 주고받는다는 이유로 REST API 라고 부르지만, REST 가 말하는 원칙과는 거리가 있습니다.
이번 글에서는 REST 의 핵심 제약과 자원 중심 URL 설계, 무상태가 주는 이점을 정리합니다. 응답을 바로 줄 수 없는 긴 작업을 REST 로 표현하는 방법도 함께 다룹니다.
1. REST 는 규격이 아니라 제약의 묶음
REST(Representational State Transfer) 는 로이 필딩이 2000년 논문에서 정리한 아키텍처 스타일입니다. HTTP 처럼 정해진 프로토콜 규격이 아니라, 지키면 특정 성질을 얻는 설계 제약의 묶음입니다.
핵심 제약은 클라이언트와 서버의 분리, 무상태, 캐시 가능성, 균일한 인터페이스, 계층 구조입니다. 실무에서 REST API 라고 하면 보통 자원을 URI 로 식별하고, 행위를 HTTP 메서드로, 결과를 상태 코드로 표현하는 수준을 뜻합니다. 응답의 링크를 따라 다음 동작을 찾는 HATEOAS 까지 구현한 API 는 많지 않습니다.
2. 주소는 명사, 동작은 메서드
균일한 인터페이스의 효과는 URL 을 나란히 놓으면 바로 드러납니다. 동사를 URL 에 넣으면 기능마다 규칙을 새로 외워야 하지만, 자원을 명사로 두면 메서드 의미만 알아도 동작을 예측할 수 있습니다.
REST 는 도서관의 청구기호와 정해진 대출·반납 규칙이에요. 책마다 창구를 따로 외우지 않아요.
- POST /getJob?id=42조회인데 POST
- POST /deleteJob?id=42동작이 URL 에
- 200 {"error": ...}실패도 200
- GET /jobs/42조회, 캐시 가능
- DELETE /jobs/42삭제, 멱등
- 404 Not Found코드로 실패 표현
GET /jobs/42 → 200
GET /jobs?status=done → 200 # 필터는 쿼리로
POST /jobs → 201 + Location: /jobs/42
PATCH /jobs/42 → 200 # 일부 수정
DELETE /jobs/42 → 204규칙을 맞추면 공짜로 얻는 것이 많습니다. GET 은 안전하므로 프록시와 브라우저가 캐시할 수 있고, PUT 과 DELETE 는 멱등이라 재시도하기 쉽습니다. 상태 코드가 정확하면 모니터링 도구도 응답 본문을 해석하지 않고 실패율을 계산합니다.
3. 무상태와 수평 확장
무상태(stateless) 는 서버가 요청 사이의 클라이언트 상태를 기억하지 않는다는 뜻입니다. 요청 하나에 처리에 필요한 정보가 모두 담겨 있어야 합니다. 로그인을 유지할 수 없다는 뜻은 아닙니다. 클라이언트가 요청마다 토큰이나 세션 ID 를 보내고, 서버는 그 값으로 매번 누구인지 확인합니다.
사서는 지난 방문을 기억하지 않아서, 올 때마다 회원증을 같이 내야 해요.
이렇게 하면 어느 서버가 요청을 받아도 같은 결과를 낼 수 있어 서버를 늘리기 쉽습니다. 세션 같은 상태는 사라지는 것이 아니라 Redis 나 DB 같은 공유 저장소로 옮겨 갑니다.
4. 긴 작업은 작업 자체를 자원으로
보고서 생성처럼 몇 분 걸리는 작업은 응답이 올 때까지 연결을 붙잡으면 프록시나 게이트웨이의 타임아웃에 끊깁니다. 그러면 작업은 끝까지 돌았는데 호출자만 실패를 받습니다.
이럴 때는 작업 자체를 자원으로 만듭니다. POST 로 접수하면 202 Accepted 와 상태를 확인할 주소를 담은 Location 헤더를 돌려줍니다. 클라이언트는 그 주소로 진행 상황을 확인합니다.
긴 작업 API 는 세탁소의 맡김표예요. 맡기고 표를 받아 가서, 나중에 표로 찾아요.
POST /jobs {"type": "monthly-report"}
→ 202 Accepted
Location: /jobs/42
GET /jobs/42
→ 200 {"status": "running"}
GET /jobs/42
→ 200 {"status": "done", "result": "/jobs/42/report"}202 는 접수했다는 뜻일 뿐 성공을 보장하지 않습니다. 작업 실패는 상태 조회 응답의 본문으로 알려 줍니다. 대신 서버에는 작업 상태를 저장할 곳이 필요하고, 여러 서버가 같은 상태를 봐야 하므로 공유 저장소에 둡니다.
완료를 확인하는 방법은 폴링과 웹훅이 대표적입니다. 폴링은 클라이언트가 주기적으로 상태를 묻는 방식이고, 웹훅은 작업이 끝나면 서버가 미리 받아 둔 주소로 알려 주는 방식입니다.
한눈에 비교
| REST | GraphQL | gRPC | |
|---|---|---|---|
| 잘 맞는 곳 | 공개 API, 일반 웹 서비스 | 화면마다 필요한 필드가 다른 앱 | 서비스 간 고성능 통신 |
| 데이터 형식 | 주로 JSON | JSON | Protocol Buffers |
| HTTP 캐시 | 그대로 활용 | 별도 설계 필요 | 사용하기 어려움 |
| 약점 | 요청 횟수 증가, 과다 응답 | 쿼리 비용 제어 | 브라우저 직접 호출 제약 |
헷갈리기 쉬운 점
- JSON 을 HTTP 로 보내면 REST 라는 오해. 동사형 URL, 모든 요청을 POST 로 처리, 에러도 200 으로 응답한다면 원칙과 멉니다.
- 무상태면 로그인을 유지할 수 없다는 오해. 서버가 기억하지 않을 뿐이고, 클라이언트가 요청마다 인증 정보를 보냅니다.
- 202 를 성공으로 처리하는 실수. 접수만 된 상태이므로 최종 결과는 상태 조회로 확인합니다.
면접에서 자주 받는 질문
폴링, 웹훅, SSE 는 언제 쓰나요?
폴링은 구현이 단순하지만 빈 요청이 많습니다. 웹훅은 서버끼리 연동할 때 좋지만 재시도와 서명 검증이 필요합니다. 브라우저에 진행률을 보여 줄 때는 SSE 나 웹소켓으로 밀어 주는 경우가 많습니다.
작업 취소는 REST 로 어떻게 표현하나요?
DELETE /jobs/42 로 지우거나, 기록을 남겨야 하면 POST /jobs/42/cancel 처럼 하위 자원을 둡니다. 이미 끝난 작업이면 409 Conflict 로 답합니다.
REST, GraphQL, gRPC 는 언제 각각 쓰나요?
REST 는 단순하고 HTTP 캐시를 그대로 쓸 수 있습니다. GraphQL 은 화면이 필요한 필드를 한 번에 받을 때 좋습니다. gRPC 는 서비스 사이의 고성능 통신과 스트리밍에 씁니다.
정리
- REST 는 규격이 아니라 자원, 메서드, 상태 코드, 무상태를 중심으로 한 설계 제약입니다.
- 무상태는 서버 확장을 쉽게 하는 대신 상태를 공유 저장소로 옮깁니다.
- 긴 작업은 작업 자체를 자원으로 만들고 202 와 Location 으로 응답합니다.
주소는 명사로, 동작은 메서드로 나누고, 오래 걸리는 일은 202 라는 맡김표를 먼저 건네면 됩니다.