C.W.K.
Stream
Lesson 03 of 07 · published

HATEOAS — Fielding 이 실제 쓴 것 (순수 버전)

~12 min · rest-design, hateoas, fielding, pure

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"REST API 는 hypertext-driven 이어야 한다. 아니면 RESTful 아니다." — Roy Fielding, 2008. 8년 동안 industry 가 그가 REST 로 인정한 적 없는 것들을 'REST' 라고 불러서, 2000년 dissertation 에서 자기가 한 말 의미 명확히 함.

Dissertation 정직하게 읽기

Roy Fielding 의 2000년 박사논문 Architectural Styles and the Design of Network-based Software Architectures 가 REST 를 여섯 제약 가진 architectural style 로 정의. 다섯 — client-server, statelessness, cacheability, layered system, uniform interface — 가 사람들이 REST API 라 부르는 것에 널리 채택. 여섯 번째가 대부분 API 가 건너뛰는 것: HATEOAS — Hypermedia As The Engine Of Application State.

HATEOAS 말함: REST client 는 API 의 진입점 URI 정확히 하나만 알아야. 나머지 — 취할 수 있는 action, navigate 할 수 있는 resource, 결과 다음 페이지, 관련 entity — 가 server 가 response 에 포함한 link 따라가서 발견. Client 가 /users/{id}/orders 하드코딩 안 함; client 가 /users/{id} GET 하고 rel="orders" 가진 link 봐서 order 위해 어디 가야 할지 알아.

HTML 유추

현장에서 HATEOAS 의 가장 깔끔한 예가 HTML 웹. 웹사이트 열 때 브라우저는 사이트 가진 URL 모름. 페이지 봄; 페이지가 link 포함; 하나 클릭; 더 많은 link 가진 새 페이지 받음. Application state 가 hypermedia 통해 진화. 브라우저가 원조 진짜 REST client — generic, link 따라가고 form 채우는 법만 앎.

Fielding 주장: JSON API 같은 방식 동작 가능. Response 가 hypermedia (JSON 안 embedded 된 link); client 가 generic (relationship 이름으로 link 걸음); server 가 URI control 유지 — client 가 절대 하드코딩 안 해서.

순수 HATEOAS response 가 어떻게 생겼나

여러 hypermedia JSON format 있고; 가장 흔한 게 HAL (Hypertext Application Language). HAL response 가 이렇게 생김:

{
  "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" }
  }
}

Client 가 resource 읽고 _links 봐서 다음에 뭘 할 수 있는지 발견. Order 나열엔 "orders" link 따라. 편집은 "edit" 따라. 절대 string template 으로 URI 안 만듦; server 가 건넨 거 따라감.

HATEOAS 가 client 를 URI 구조에서 decouple. Server 가 /users/{id}/orders/customers/{id}/purchases 로 바꾸면 하드코딩 URI client 다 깸. HATEOAS client 는 user resource 에서 orders-rel link 따라가 — 어디 가리키든. Server 가 URI 자유롭게 진화 가능; client 가 계속 동작.

왜 강력 (이론적으로)

  • URI 진화. Server 가 URI 마음대로 바꿈; client 가 relationship 이름으로 navigate 라서 신경 안 씀.
  • 동적 능력 발견. Server 가 user 권한, resource state, 비즈니스 규칙에 기반해서 link 포함하거나 생략 — client 코드 변경 없이 client 동작 변경.
  • Generic client. 브라우저가 server-specific 코드 없이 어느 HTML 사이트와도 상호작용 가능. 진짜 HATEOAS client 가 JSON API 에 같은 거 할 수 있어.
  • Workflow encoding. Server 가 state machine 통제. Draft 글이 "publish" link 가짐; 일단 publish 되면 안 가짐. Client 가 사용 가능 link 읽고 비즈니스 로직 없이 올바른 UI render.

Workflow 예

Draft, review, publish 될 수 있는 글 상상. 사용 가능 전환이 현재 state 에 의존. 순수 HATEOAS 에선 server 가 이 전환을 link 로 표현:

# 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" }
  }
}

Client 가 어떤 link 존재하나 보고 맞는 버튼 render. 글 state machine 의 client 쪽 지식 없음. 새 전환 가진 새 state 추가; 기존 client 가 코드 변경 없이 새 버튼 보여줘.

Format 들과 왜 중요한지

주목할 hypermedia JSON format 셋:

  • HAL (application/hal+json) — 최소, link + embedded sub-resource. 가장 채택됨.
  • JSON:API (application/vnd.api+json) — 더 무거움, relationship/pagination/sparse fieldset 에 대해 의견 있음.
  • Siren (application/vnd.siren+json) — 입력 필드 가진 action 추가, 진짜 hypermedia 에 더 가까움.

HTML 자체가 ship 된 가장 성공한 hypermedia format. 브라우저가 proof of concept; 아무도 HTML+브라우저가 동작한다는 거 안 의심.

cwkPippa 의 HATEOAS 현실

cwkPippa API 는 본질적으로 HATEOAS 영 — frontend (in frontend/src/lib/api.ts) 가 호출하는 모든 URI 하드코딩. 201 Created 의 Location header 가 hypermedia 에 가장 가까운 것; 그게 다임. cwkPippa 엔 괜찮 — 유일 client 가 같은 repo 의 React frontend, 같이 deploy. HATEOAS 비용이 아무것도 사주지 않음. Lesson 3.4 가 그 계산이 뒤집히는 때 설명.

Code

HAL response — relationship 이름으로 link, embedded sub-resource·json
// 순수 HAL response — link 가 모든 거 이끔
{
  "id": "u_42",
  "name": "Pippa",
  "email": "pippa@example.com",
  "_links": {
    "self":     { "href": "/users/u_42" },
    "orders":   { "href": "/users/u_42/orders", "title": "User 의 order" },
    "avatar":   { "href": "/users/u_42/avatar" },
    "edit":     { "href": "/users/u_42", "method": "PUT" },
    "delete":   { "href": "/users/u_42", "method": "DELETE" }
  },
  "_embedded": {
    "latest-order": {
      "id": "o_xyz",
      "_links": { "self": { "href": "/orders/o_xyz" } }
    }
  }
}
HATEOAS client — rel 로 navigate, 절대 URI 안 만듦·python
# HATEOAS client — 진입점 넘어 URI 절대 하드코딩 안 함
import httpx

class HateoasClient:
    def __init__(self, entry_point: str):
        self.entry = entry_point
        self.client = httpx.Client()

    def root(self):
        return self.client.get(self.entry).json()

    def follow(self, response: dict, rel: str, method: str = 'GET', **kwargs):
        """Relationship 이름으로 link 따라."""
        link = response['_links'].get(rel)
        if not link:
            raise RuntimeError(f'rel={rel} 의 link 없음; 사용 가능: {list(response["_links"].keys())}')
        method = link.get('method', method)
        return self.client.request(method, link['href'], **kwargs).json()

# 사용: client 가 /users/u_42/orders 명시적으로 절대 모름
client = HateoasClient('https://api.example.com/')
root = client.root()
user = client.follow(root, 'user-by-id', params={'id': 'u_42'})
orders = client.follow(user, 'orders')
for order_link in user['_links'].get('related-orders', []):
    detail = client.follow({'_links': {'self': order_link}}, 'self')
# Server 가 /users/u_42/orders 를 /customers/u_42/purchases 로 옮겼어?
# Server 가 'orders' rel update; 이 client 계속 동작.
Hypermedia 의 state machine — link 가 허용 전환 표현·json
// 사용 가능 link 통한 state machine — 같은 resource, state 당 다른 link

// Draft article — submit-review 와 delete 만 가능
{
  "id": "a_42", "status": "draft",
  "_links": {
    "self":          { "href": "/articles/a_42" },
    "submit-review": { "href": "/articles/a_42/review", "method": "POST" },
    "delete":        { "href": "/articles/a_42",        "method": "DELETE" }
  }
}

// In-review article — 다른 전환
{
  "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" }
  }
}

// Client 가 존재하는 link 에 매칭 버튼 render.
// 새 state 추가? 새 link 추가. Client 가 코드 변경 없이 적응.

External links

Exercise

작은 JSON API 골라 (네 거나 resource 3-4 개 가진 거 만들어). Resource 하나에 HAL-스타일 HATEOAS response 어떻게 생겼을지 스케치. 그 다음 API root URL 만 받고 client 코드에서 절대 URI 안 만들고 rel-이름 link 따라 특정 resource 까지 걷는 generic client 써. 주의: server 가 relationship 이름 바꾸면? Link 에 인증 추가해야 하면? Developer experience 를 일반 하드코딩 URI client 와 비교.
Hint
Generic client 가 response._links[rel].href 찾아서 HTTP-fetch 하는 follow(response, rel) method 가져야. 요점은 HATEOAS 가 왜 무겁게 느껴지는지 보는 것: 모든 navigation 이 lookup-then-fetch, error handling 이 누락된 rel 책임져야, debugging 이 link 검사 필요. 이 비용 실제 — Lesson 3.4 가 이득에 대해 정확히 이것 weigh.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.