"REST API는 하이퍼텍스트가 주도해야 한다. 그렇지 않으면 RESTful하지 않다." — Roy Fielding, 2008. REST라는 이름이 자신의 정의와 다르게 쓰이는 상황에서, 2000년 박사논문의 뜻을 다시 분명히 밝힌 글이야.
박사논문을 정확히 읽기
Roy Fielding의 2000년 박사논문 Architectural Styles and the Design of Network-based Software Architectures는 REST를 클라이언트-서버, 무상태성, 캐시, 일관된 인터페이스, 계층형 시스템, 선택적인 code-on-demand 제약으로 설명해. HATEOAS는 별도의 여섯 번째 제약이 아니라 HATEOAS — Hypermedia as the Engine of Application State라는 이름으로 흔히 요약되는 일관된 인터페이스의 핵심 요소야. 오늘날 REST API라고 불리는 많은 설계가 바로 이 하이퍼미디어 제약을 충분히 적용하지 않아.
원칙은 이래. 클라이언트는 초기 진입 URI 하나와 표준화된 미디어 타입, 링크 관계와 동작의 의미를 알고 시작해. 그다음 가능한 동작과 관련 리소스, 다음 페이지 같은 상태 전환은 서버가 표현에 실어 보낸 하이퍼미디어를 통해 발견해. 클라이언트는 /users/{id}/orders를 임의로 조합하지 않고, /users/{id}를 GET 한 뒤 rel="orders" 링크가 가리키는 곳을 따라가. 'URI 하나만 알면 모든 의미를 저절로 이해한다'는 뜻은 아니야. 관계와 미디어 타입의 의미는 여전히 공유 계약으로 남아.
HTML 비유
HATEOAS를 이해하기 가장 좋은 사례는 HTML 웹이야. 브라우저는 어떤 사이트의 URL을 전부 미리 알지 못해. 첫 페이지를 받아 링크를 따라가고, 양식에 입력한 뒤 서버가 제시한 동작을 수행해. 애플리케이션 상태가 하이퍼미디어를 통해 전개되는 셈이야. 브라우저는 HTML의 링크와 양식 의미를 아는 범용 클라이언트고.
JSON 기반 API도 같은 원리로 설계할 수 있어. 응답에 링크와 동작 설명을 넣고, 클라이언트는 합의된 관계 이름과 미디어 타입을 해석해 다음 요청을 선택해. 그러면 URI 구조에 대한 결합은 줄어들지만 사라지지는 않아. 관계 이름과 표현 형식의 계약은 안정적으로 유지해야 하고, 외부에 이미 공유된 URI의 호환성도 별도로 고려해야 해.
HATEOAS 응답은 어떻게 생길까
JSON 하이퍼미디어 형식은 여러 가지야. HAL(Hypertext Application Language)은 그중 널리 알려진 형식이고, 아래 예시는 HAL의 링크 구조에 동작 정보를 덧붙여 설명한 형태야. method 속성은 HAL 핵심 명세가 정한 표준 동작 표현이 아니라 이 예제의 확장이야:
{
"id": "u_42",
"name": "Pippa",
"_links": {
"self": { "href": "/users/u_42" },
"orders": { "href": "/users/u_42/orders" },
"edit": { "href": "/users/u_42", "method": "PUT" },
"delete": { "href": "/users/u_42", "method": "DELETE" }
}
}
클라이언트는 리소스를 읽고 _links에서 다음 탐색 경로를 찾아. 주문 목록으로 갈 때는 "orders" 관계를 따라가고, 수정이나 삭제처럼 입력과 메서드 정보가 필요한 동작은 해당 미디어 타입이 정의한 규칙으로 해석해야 해. 핵심은 URI 문자열을 추측해 조합하는 대신 서버가 제공한 전환을 따른다는 점이야.
/users/{id}/orders를 /customers/{id}/purchases로 옮겨도 클라이언트가 orders 관계를 해석해 따라간다면 URI 템플릿을 바꿀 필요가 없어. 다만 관계 의미와 미디어 타입 계약은 그대로 유지되어야 하고, 저장된 북마크나 외부에 공유된 직접 URI까지 자동으로 보호되는 것은 아니야. 자유로운 변경이 아니라 결합 지점의 이동에 가까워.왜 강력한가
- URI 진화의 여지. 클라이언트가 관계 이름으로 이동하면 서버는 일부 내부 URI 구조를 바꿀 여지가 생겨. 안정된 관계 계약과 기존 직접 링크의 호환성은 여전히 관리해야 해.
- 동적 기능 발견. 서버는 사용자 권한, 리소스 상태, 비즈니스 규칙에 따라 링크나 동작을 넣고 빼서 현재 가능한 전환을 응답에 직접 표현할 수 있어.
- 범용 처리 가능성. 브라우저가 HTML의 표준 의미로 여러 사이트를 다루듯, 충분히 표준화된 JSON 하이퍼미디어 형식과 관계 어휘가 있다면 공통 클라이언트 계층을 만들 수 있어. 실제 범용성은 그 표준과 도구가 얼마나 널리 공유되는지에 달려 있고.
- 작업 흐름 표현. 서버가 상태에 맞는 전환을 제공할 수 있어. 초안에는 "발행" 동작이 보이고, 발행 뒤에는 사라지는 식이야. 클라이언트는 허용된 동작을 표시하되, 의미 있는 UI와 입력 검증까지 모두 자동으로 얻는 것은 아니야.
작업 흐름 예
초안에서 검토를 거쳐 발행되는 글을 생각해 봐. 가능한 전환은 현재 상태에 따라 달라지고, HATEOAS 방식에서는 서버가 그 전환을 링크나 동작으로 표현해:
# Draft state
{
"id": "a_42", "status": "draft", "title": "...",
"_links": {
"self": { "href": "/articles/a_42" },
"submit-review": { "href": "/articles/a_42/review", "method": "POST" },
"delete": { "href": "/articles/a_42", "method": "DELETE" }
}
}
# Review 제출 후 — 다른 link
{
"id": "a_42", "status": "in-review",
"_links": {
"self": { "href": "/articles/a_42" },
"approve":{ "href": "/articles/a_42/publish", "method": "POST" },
"reject": { "href": "/articles/a_42/reject", "method": "POST" }
}
}
클라이언트는 현재 제공된 전환을 보고 알맞은 버튼을 그릴 수 있어. 범용 동작 렌더러라면 새 관계를 어느 정도 자동 표시할 수 있지만, 새 동작의 문구와 입력 형식, 사용자 경험까지 이해하려면 미디어 타입의 표준 정의나 클라이언트 변경이 필요할 수 있어. HATEOAS는 상태 전환의 출처를 서버로 모으지, 도메인 의미 자체를 없애지는 않아.
형식이 중요한 이유
살펴볼 만한 JSON 하이퍼미디어 형식은 셋이야:
- HAL(
application/hal+json) — 링크와 포함된 리소스를 단순한 구조로 표현하는 잘 알려진 형식이야. - JSON:API(
application/vnd.api+json) — 리소스와 관계, 페이지 나누기, sparse fieldsets 등에 강한 규칙을 둔 형식이야. 하이퍼미디어 사용 수준은 API 설계에 따라 달라져. - Siren(
application/vnd.siren+json) — 입력 필드를 포함한 동작을 표현해 링크만 있는 형식보다 작업 흐름을 더 풍부하게 설명할 수 있어.
HTML은 지금까지 가장 성공적으로 배포된 하이퍼미디어 형식이야. 성공의 핵심은 링크만 존재한다는 데 있지 않아. 브라우저와 서버가 링크, 양식, 메서드, 미디어 타입의 의미를 폭넓게 공유한다는 데 있어.
cwkPippa의 HATEOAS 현실
frontend/src/lib/api.ts)가 호출할 URI를 알고 있고, 201 Created의 Location 헤더가 가장 가까운 하이퍼미디어 힌트야. 단일 React 프런트엔드가 같은 저장소에서 함께 배포되는 구조라면 이 선택이 충분히 합리적이야. HATEOAS를 추가하면 링크 모델과 클라이언트 해석 계층을 함께 유지해야 하므로, 얻는 결합 완화보다 비용이 클 수 있어. 다음 과에서 그 비용과 이득이 언제 뒤집히는지 살펴봐.