REST API는 HATEOAS 하이퍼미디어 링크를 구현해야 하는가?
요약
HATEOAS(Hypermedia as the Engine of Application State)는 이론적으로는 우아하지만 실제로는 복잡합니다. 대부분의 API는 완전한 HATEOAS를 건너뛰고 페이지 매김, 관련 리소스, 작업에 선택적인 하이퍼미디어 링크를 사용합니다. Modern PetstoreAPI는 클라이언트가 완전히 하이퍼미디어 기반이 되도록 강요하지 않으면서 실용적인 하이퍼미디어 링크를 구현합니다.
서론
REST API 설계에 대해 읽고 있다고 가정해 봅시다. 당신은 HATEOAS(Hypermedia as the Engine of Application State)를 접하게 됩니다. 설명에는 이렇게 쓰여 있습니다. "클라이언트는 URL을 하드코딩하는 것이 아니라 하이퍼미디어 링크를 통해 모든 작업을 발견해야 합니다."
당신은 생각합니다. "복잡하게 들리는데, 실제로 이렇게 하는 사람이 있을까?"
정답은 "그렇지 않다"입니다. HATEOAS는 가장 많이 건너뛰는 REST 제약 조건입니다. REST를 발명한 로이 필딩(Roy Fielding)은 이것이 필수적이라고 말합니다. 대부분의 API 설계자들은 이것이 비실용적이라고 말합니다. 결과적으로 대부분의 "REST" API는 필딩의 정의에 따르면 진정한 RESTful이 아닙니다.
Modern PetstoreAPI는 실용적인 접근 방식을 취합니다. 즉, 가치를 더하는 곳(페이지 매김, 관련 리소스, 작업)에 하이퍼미디어 링크를 사용하지만 클라이언트가 완전히 하이퍼미디어 기반이 되도록 강요하지 않습니다.
이 가이드에서는 HATEOAS가 무엇인지, 왜 논란의 여지가 있는지, 그리고 Modern PetstoreAPI를 참고하여 실용적인 하이퍼미디어 링크를 구현하는 방법을 배웁니다.
HATEOAS란 무엇인가?
HATEOAS는 클라이언트가 문서가 아닌 하이퍼미디어 링크를 통해 API 기능을 발견해야 한다고 말하는 REST 제약 조건입니다.
개념
URL을 하드코딩하는 대신:
// 클라이언트가 URL을 하드코딩합니다.
const response = await fetch('https://petstoreapi.com/v1/pets/123');
const pet = await response.json();
// 클라이언트가 URL 구조를 알고 있습니다.
await fetch(`https://petstoreapi.com/v1/pets/${pet.id}/orders`);
클라이언트는 응답에서 링크를 따라갑니다:
// 클라이언트가 루트에서 시작합니다.
const root = await fetch('https://petstoreapi.com/v1');
const rootData = await root.json();
// 클라이언트가 애완동물 링크를 따라갑니다.
const petsUrl = rootData._links.pets.href;
const pets = await fetch(petsUrl);
const petsData = await pets.json();
// 클라이언트가 특정 애완동물 링크를 따라갑니다.
const petUrl = petsData._links.self.href;
const pet = await fetch(petUrl);
const petData = await pet.json();
// 클라이언트가 주문 링크를 따라갑니다.
const ordersUrl = petData._links.orders.href;
const orders = await fetch(ordersUrl);
이론
HATEOAS를 사용하면:
1. 클라이언트는 URL을 하드코딩하지 않습니다.
클라이언트가 깨지지 않고도 URL을 변경할 수 있습니다. 서버가 URL 구조를 제어합니다.
2. 클라이언트는 기능을 발견합니다.
링크가 존재하면 해당 작업을 사용할 수 있습니다. 그렇지 않으면 사용할 수 없습니다(또는 이 사용자에게 허용되지 않습니다).
3. API는 자가 문서화됩니다.
클라이언트는 웹사이트를 탐색하듯이 링크를 따라 API를 탐색합니다.
예시: 완전한 HATEOAS 응답
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT",
"status": "AVAILABLE",
"_links": {
"self": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24"
},
"update": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"method": "PUT"
},
"delete": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"method": "DELETE"
},
"orders": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders"
},
"adopt": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt",
"method": "POST"
}
}
}
클라이언트는 URL 패턴을 알 필요가 없습니다. 링크를 따라갑니다.
HATEOAS 논쟁
HATEOAS는 이론과 실제가 다르기 때문에 논란의 여지가 있습니다.
HATEOAS 찬성론
1. 느슨한 결합
클라이언트는 URL 구조에 의존하지 않습니다. 서버는 클라이언트를 깨뜨리지 않고 URL을 변경할 수 있습니다.
2. 발견 가능성
클라이언트는 문서를 읽지 않고도 API를 탐색할 수 있습니다.
3. 상태 기반 작업
링크는 사용 가능한 작업을 보여줍니다. 애완동물이 입양되면 "입양" 링크가 사라집니다.
4. 진정한 REST
로이 필딩은 HATEOAS가 REST에 필수적이라고 말합니다. 그것이 없으면 REST를 하고 있는 것이 아닙니다.
HATEOAS 반대론
1. 복잡성
클라이언트는 하이퍼미디어 파싱 로직이 필요합니다. 간단한 HTTP 클라이언트는 복잡한 상태 머신이 됩니다.
2. 성능
클라이언트는 URL을 발견하기 위해 여러 요청을 합니다. 직접적인 URL 접근이 더 빠릅니다.
3. 디버깅 어려움
링크를 따라가는 것은 디버깅을 더 어렵게 만듭니다. 단순히 URL을 curl할 수 없습니다. 링크 체인을 따라가야 합니다.
4. 열악한 툴링
대부분의 HTTP 클라이언트, 테스트 도구 및 문서 생성기는 하드코딩된 URL을 가정합니다.
5. 아무도 사용하지 않음
GitHub, Stripe, Twilio, Twitter 등 주요 API는 완전한 HATEOAS를 사용하지 않습니다. 그들이 필요하지 않다면, 당신도 필요할까요?
현실
대부분의 API는 "REST"라고 주장하지만 HATEOAS를 건너뜁니다. 그들은 사실 "HTTP API" 또는 "REST-유사 API"입니다. 진정한 REST(HATEOAS 포함)는 드뭅니다.
실용적인 하이퍼미디어 링크
완전한 HATEOAS 대신, 가치를 더하는 곳에 하이퍼미디어 링크를 사용하세요.
1. 페이지 매김 링크
문제: 클라이언트가 페이지 매김 URL을 구성해야 합니다.
해결책: 다음/이전 링크를 제공합니다.
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"totalPages": 10
},
"links": {
"self": "https://petstoreapi.com/v1/pets?page=2&limit=20",
"first": "https://petstoreapi.com/v1/pets?page=1&limit=20",
"prev": "https://petstoreapi.com/v1/pets?page=1&limit=20",
"next": "https://petstoreapi.com/v1/pets?page=3&limit=20",
"last": "https://petstoreapi.com/v1/pets?page=10&limit=20"
}
}
장점: 클라이언트는 페이지 매김 URL을 구성하지 않고 링크를 따라갑니다.
2. 관련 리소스 링크
문제: 클라이언트가 관련 리소스의 URL 패턴을 알아야 합니다.
해결책: 관련 리소스에 대한 링크를 제공합니다.
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"_links": {
"self": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"orders": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders",
"owner": "https://petstoreapi.com/v1/users/019b4127-54d5-76d9-b626-0d4c7bfce5b6"
}
}
장점: 클라이언트는 문서 없이 관련 리소스를 발견합니다.
3. 작업 링크
문제: 클라이언트가 사용 가능한 작업을 알아야 합니다.
해결책: 사용 가능한 작업에 대한 링크를 제공합니다.
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"status": "AVAILABLE",
"_links": {
"adopt": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt",
"method": "POST"
}
}
}
애완동물이 이미 입양된 경우:
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"status": "ADOPTED",
"_links": {
// "adopt" 링크 없음 - 작업 사용 불가
}
}
장점: 링크는 상태에 따라 사용 가능한 작업을 나타냅니다.
4. 커서 기반 페이지 매김
문제: 클라이언트가 커서 URL을 구성해야 합니다.
해결책: 불투명한 다음/이전 URL을 제공합니다.
{
"data": [...],
"links": {
"next": "https://petstoreapi.com/v1/pets?cursor=eyJpZCI6IjAxOWI0MTMyIn0"
}
}
장점: 클라이언트는 커서를 파싱하지 않고 링크를 따라갑니다.
Modern PetstoreAPI가 하이퍼미디어를 사용하는 방법
Modern PetstoreAPI는 선택적인 하이퍼미디어 링크를 사용합니다.
페이지 매김 링크
모든 컬렉션 엔드포인트에는 페이지 매김 링크가 포함됩니다:
GET /v1/pets?limit=20
{
"data": [...],
"pagination": {
"limit": 20,
"hasMore": true
},
"links": {
"self": "https://petstoreapi.com/v1/pets?limit=20",
"next": "https://petstoreapi.com/v1/pets?cursor=eyJpZCI6IjAxOWI0MTMyIn0&limit=20"
}
}
관련 리소스 링크
리소스 응답에는 관련 리소스에 대한 링크가 포함됩니다:
GET /v1/pets/019b4132-70aa-764f-b315-e2803d882a24
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"ownerId": "019b4127-54d5-76d9-b626-0d4c7bfce5b6",
"_links": {
"self": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"owner": "https://petstoreapi.com/v1/users/019b4127-54d5-76d9-b626-0d4c7bfce5b6",
"orders": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders"
}
}
완전한 HATEOAS 없음
Modern PetstoreAPI는 클라이언트가 하이퍼미디어 기반이 되도록 요구하지 않습니다. 클라이언트는 다음을 수행할 수 있습니다:
옵션 1: 링크 따라가기 (하이퍼미디어 기반)
const pet = await fetch(petUrl);
const ownerUrl = pet._links.owner.href;
const owner = await fetch(ownerUrl);
옵션 2: URL 구성하기 (전통적 방식)
const pet = await fetch(`https://petstoreapi.com/v1/pets/${petId}`);
const owner = await fetch(`https://petstoreapi.com/v1/users/${pet.ownerId}`);
두 가지 접근 방식 모두 작동합니다. 링크는 편의를 위해 제공되며, 강제 사항이 아닙니다.
Apidog로 하이퍼미디어 API 테스트하기
Apidog는 하이퍼미디어 링크를 테스트하고 링크의 정확성을 검증하는 데 도움을 줍니다.
링크 존재 여부 테스트
응답에 예상 링크가 포함되어 있는지 확인합니다:
// Apidog 테스트 스크립트
pm.test("응답에 페이지 매김 링크가 포함되어 있습니다.", () => {
const links = pm.response.json().links;
pm.expect(links).to.have.property('self');
pm.expect(links).to.have.property('next');
});
링크 유효성 테스트
링크를 따라가서 작동하는지 확인합니다:
// Apidog 테스트 스크립트
const nextUrl = pm.response.json().links.next;
pm.sendRequest(nextUrl, (err, response) => {
pm.test("다음 링크가 200을 반환합니다.", () => {
pm.expect(response.code).to.equal(200);
});
});
링크 형식 테스트
링크가 예상 형식을 따르는지 확인합니다:
pm.test("링크는 절대 URL입니다.", () => {
const links = pm.response.json().links;
Object.values(links).forEach(link => {
pm.expect(link).to.match(/^https:\/\//);
});
});
HATEOAS를 언제 사용해야 하는가
가치를 더하는 곳에 하이퍼미디어 링크를 사용하세요. 그렇지 않은 경우에는 건너뛰세요.
다음 경우에 하이퍼미디어 링크를 사용하세요:
1. 페이지 매김 - 클라이언트가 페이지 매김 URL을 구성해서는 안 됩니다.
2. 관련 리소스 - 편리한 탐색
3. 상태 종속 작업 - 리소스 상태에 따라 사용 가능한 작업 표시
4. 복잡한 워크플로우 - 다단계 프로세스를 통해 클라이언트 안내
다음 경우에 HATEOAS를 건너뛰세요:
1. 간단한 CRUD API - 클라이언트가 URL을 쉽게 구성할 수 있는 경우
2. 내부 API - 팀이 URL 변경을 조율할 수 있는 경우
3. 성능이 중요한 API - 추가 링크는 응답 크기를 증가시킵니다.
4. 모바일 API - 대역폭이 중요하며, 링크는 오버헤드를 추가합니다.
결론
HATEOAS는 이론적으로는 우아하지만 실제로는 복잡합니다. 대부분의 API는 완전한 HATEOAS를 건너뛰고 가치를 더하는 곳에 선택적인 하이퍼미디어 링크를 사용합니다.
Modern PetstoreAPI는 페이지 매김 링크, 관련 리소스 링크, 작업 링크와 같은 실용적인 하이퍼미디어를 보여줍니다. 클라이언트가 완전히 하이퍼미디어 기반이 되도록 강요하지 않습니다.
Apidog를 사용하여 하이퍼미디어 링크를 테스트하고, 링크의 정확성을 검증하며, API가 유용한 탐색 기능을 제공하는지 확인하세요.
주요 요점:
- 완전한 HATEOAS는 드물고 복잡합니다.
- 선택적인 하이퍼미디어 링크는 복잡성 없이 가치를 더합니다.
- 페이지 매김 링크는 가장 유용한 하이퍼미디어 기능입니다.
- 클라이언트가 하이퍼미디어 기반이 되도록 강요하지 마세요.
- 링크가 올바르게 작동하는지 테스트하세요.
실용적인 하이퍼미디어 구현을 보려면 Modern PetstoreAPI 문서를 살펴보세요.
자주 묻는 질문
REST API에 HATEOAS가 필수적인가요?
로이 필딩(REST 발명가)에 따르면 그렇습니다. 하지만 실제로는 그렇지 않습니다. 대부분의 "REST" API는 HATEOAS를 건너뛰며, 기술적으로는 "HTTP API" 또는 "REST-유사 API"입니다.
HATEOAS는 무엇의 약자인가요?
Hypermedia as the Engine of Application State(애플리케이션 상태 엔진으로서의 하이퍼미디어)의 약자입니다. 클라이언트가 하드코딩된 URL이 아닌 하이퍼미디어 링크를 통해 API 기능을 발견한다는 의미입니다.
주요 API들이 HATEOAS를 사용하나요?
아니요. GitHub, Stripe, Twilio 및 대부분의 주요 API는 완전한 HATEOAS를 사용하지 않습니다. 일부 하이퍼미디어 링크(페이지 매김, 관련 리소스)를 포함할 수 있지만 클라이언트가 하이퍼미디어 기반이 되도록 요구하지는 않습니다.
HATEOAS와 하이퍼미디어 링크의 차이점은 무엇인가요?
HATEOAS는 클라이언트가 완전히 하이퍼미디어 기반이 되도록 요구하는 제약 조건입니다. 하이퍼미디어 링크는 응답에 포함된 링크일 뿐입니다. HATEOAS를 강제하지 않고도 링크를 포함할 수 있습니다.
제 API에 HATEOAS를 구현해야 할까요?
아마도 완전한 HATEOAS는 아닐 것입니다. 페이지 매김 및 관련 리소스에 선택적인 하이퍼미디어 링크를 사용하세요. 특별한 이유가 없는 한 클라이언트가 하이퍼미디어 기반이 되도록 강요하지 마세요.
HATEOAS API를 어떻게 테스트하나요?
Apidog를 사용하여 링크 존재 여부를 확인하고, 링크를 따라가며, 링크의 정확성을 검증하세요. 링크가 예상 응답을 반환하는지 테스트하세요.
HAL 형식은 무엇인가요?
HAL(Hypertext Application Language)은 하이퍼미디어 링크를 위한 표준 형식입니다. _links 및 _embedded 필드를 사용합니다. Modern PetstoreAPI는 HAL에서 영감을 받은 링크 형식을 사용합니다.
클라이언트가 하이퍼미디어 링크를 무시할 수 있나요?
예. API가 링크를 제공하지만 클라이언트가 이를 사용하도록 요구하지 않는다면, 클라이언트는 URL을 직접 구성할 수 있습니다. 이것이 대부분의 API가 취하는 실용적인 접근 방식입니다.
