HATEOAS

#μ„œλ¬Έ: HATEOAS β€” λŒ€μ²΄ μ„€λͺ…

이 νŽ˜μ΄μ§€λŠ” HATEOAS의 μœ„ν‚€ν”Όλ””μ•„ ν•­λͺ©μ„ HTML을 μ‚¬μš©ν•΄ μž¬κ΅¬μ„±ν•œ κ²ƒμž…λ‹ˆλ‹€. 이 μ„€λͺ…은 JSON API와 λΉ„κ΅ν•˜λ©΄μ„œ κ°œλ…μ„ μ„€λͺ…ν•˜λ©°, μœ„ν‚€ν”Όλ””μ•„μ— μ ν•©ν•œ 쀑립적인 μ„€λͺ…λ³΄λ‹€λŠ” λ‹€μ†Œ 의견이 λ“€μ–΄κ°„ μ„€λͺ…μ΄μ§€λ§Œ, 우리의 κ΄€μ μ—μ„œλŠ” 더 μ •ν™•ν•˜λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

---

μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μƒνƒœμ˜ μ—”μ§„μœΌλ‘œμ„œμ˜ ν•˜μ΄νΌλ―Έλ””μ–΄(Hypermedia as the Engine of Application State, HATEOAS)λŠ” REST μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ•„ν‚€ν…μ²˜μ˜ μ œμ•½ 쀑 ν•˜λ‚˜λ‘œ, 이λ₯Ό λ‹€λ₯Έ λ„€νŠΈμ›Œν¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ•„ν‚€ν…μ²˜μ™€ ꡬ뢄 μ§“μŠ΅λ‹ˆλ‹€.

HATEOASμ—μ„œλŠ” ν΄λΌμ΄μ–ΈνŠΈκ°€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„κ°€ λ™μ μœΌλ‘œ μ œκ³΅ν•˜λŠ” ν•˜μ΄νΌλ―Έλ””μ–΄λ₯Ό 톡해 λ„€νŠΈμ›Œν¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜κ³Ό μƒν˜Έμž‘μš©ν•©λ‹ˆλ‹€. REST ν΄λΌμ΄μ–ΈνŠΈλŠ” ν•˜μ΄νΌλ―Έλ””μ–΄μ— λŒ€ν•œ 일반적인 이해 μ΄μ™Έμ—λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄λ‚˜ μ„œλ²„μ™€ μƒν˜Έμž‘μš©ν•˜λŠ” 방법에 λŒ€ν•΄ 거의 λ˜λŠ” μ „ν˜€ 사전 지식이 ν•„μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λ°˜λ©΄μ— μ˜€λŠ˜λ‚ μ˜ JSON 기반 μ›Ή ν΄λΌμ΄μ–ΈνŠΈλŠ” 주둜 μŠ€μ›¨κ±°(swagger)와 같은 도ꡬλ₯Ό 톡해 λ¬Έμ„œν™”λœ κ³ μ •λœ μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 μƒν˜Έμž‘μš©ν•©λ‹ˆλ‹€.

HATEOASκ°€ λΆ€κ³Όν•˜λŠ” μ œμ•½μ€ ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„λ₯Ό λΆ„λ¦¬μ‹œμΌœ μ€λ‹ˆλ‹€. μ΄λŠ” μ„œλ²„ κΈ°λŠ₯이 λ…λ¦½μ μœΌλ‘œ λ°œμ „ν•  수 있게 ν•΄μ€λ‹ˆλ‹€.

#μ˜ˆμ‹œ

HTTPλ₯Ό κ΅¬ν˜„ν•œ μ‚¬μš©μž μ—μ΄μ „νŠΈκ°€ κ°„λ‹¨ν•œ URL을 톡해 REST μ—”λ“œν¬μΈνŠΈμ— HTTP μš”μ²­μ„ λ³΄λƒ…λ‹ˆλ‹€. μ‚¬μš©μž μ—μ΄μ „νŠΈκ°€ μˆ˜ν–‰ν•  수 μžˆλŠ” λͺ¨λ“  후속 μš”μ²­μ€ 각 μš”μ²­μ— λŒ€ν•œ ν•˜μ΄νΌλ―Έλ””μ–΄ μ‘λ‹΅μ—μ„œ λ°œκ²¬λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ ν‘œν˜„μ— μ‚¬μš©λ˜λŠ” λ―Έλ””μ–΄ νƒ€μž…κ³Ό 그듀이 포함할 수 μžˆλŠ” 링크 κ΄€κ³„λŠ” ν‘œμ€€ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. ν΄λΌμ΄μ–ΈνŠΈλŠ” ν•˜μ΄νΌλ―Έλ””μ–΄ ν‘œν˜„ λ‚΄μ˜ 링크λ₯Ό μ„ νƒν•˜κ±°λ‚˜ ν•΄λ‹Ή λ―Έλ””μ–΄ νƒ€μž…μ΄ μ œκ³΅ν•˜λŠ” λ‹€λ₯Έ λ°©λ²•μœΌλ‘œ ν‘œν˜„μ„ μ‘°μž‘ν•¨μœΌλ‘œμ¨ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μƒνƒœλ₯Ό μ „ν™˜ν•©λ‹ˆλ‹€.

μ΄λ ‡κ²Œ RESTful μƒν˜Έμž‘μš©μ€ μ™ΈλΆ€ 정보가 μ•„λ‹Œ ν•˜μ΄νΌλ―Έλ””μ–΄μ— μ˜ν•΄ κ΅¬λ™λ©λ‹ˆλ‹€.

ꡬ체적인 예λ₯Ό 톡해 이λ₯Ό λͺ…ν™•νžˆ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. μ›Ή λΈŒλΌμš°μ €κ°€ 은행 κ³„μ’Œ λ¦¬μ†ŒμŠ€λ₯Ό κ°€μ Έμ˜€λŠ” GET μš”μ²­μ„ λ°œν–‰ν•œλ‹€κ³  κ°€μ •ν•΄ λ΄…μ‹œλ‹€:

GET /accounts/12345 HTTP/1.1
Host: bank.example.com

μ„œλ²„λŠ” HTML을 μ‚¬μš©ν•œ ν•˜μ΄νΌλ―Έλ””μ–΄ ν‘œν˜„μœΌλ‘œ μ‘λ‹΅ν•©λ‹ˆλ‹€:

HTTP/1.1 200 OK

<html>
  <body>
    <div>κ³„μ’Œ 번호: 12345</div>
    <div>μž”μ•‘: $100.00 USD</div>
    <div>링크:
        <a href="/accounts/12345/deposits">μž…κΈˆ</a>
        <a href="/accounts/12345/withdrawals">좜금</a>
        <a href="/accounts/12345/transfers">이체</a>
        <a href="/accounts/12345/close-requests">κ³„μ’Œ 폐쇄 μš”μ²­</a>
    </div>
  <body>
</html>

이 μ‘λ‹΅μ—λŠ” λ‹€μŒκ³Ό 같은 κ°€λŠ₯ν•œ 후속 μž‘μ—…μ΄ ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€: μž…κΈˆ, 좜금, 이체λ₯Ό μœ„ν•΄ UI둜 μ΄λ™ν•˜κ±°λ‚˜ κ³„μ’Œ 폐쇄 μš”μ²­μ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‚˜μ€‘μ— κ³„μ’Œκ°€ 초과 인좜된 ν›„μ˜ 상황을 생각해 λ΄…μ‹œλ‹€. 이제 이 κ³„μ’Œ μƒνƒœ λ³€ν™”λ‘œ 인해 μ‚¬μš© κ°€λŠ₯ν•œ 링크 μ„ΈνŠΈκ°€ λ‹¬λΌμ§‘λ‹ˆλ‹€.

HTTP/1.1 200 OK

<html>
  <body>
    <div>κ³„μ’Œ 번호: 12345</div>
    <div>μž”μ•‘: -$50.00 USD</div>
    <div>링크:
        <a href="/accounts/12345/deposits">μž…κΈˆ</a>
    </div>
  <body>
</html>

이제 μ‚¬μš© κ°€λŠ₯ν•œ λ§ν¬λŠ” ν•˜λ‚˜λΏμž…λ‹ˆλ‹€: 더 λ§Žμ€ λˆμ„ μž…κΈˆν•˜λŠ” λ§ν¬μž…λ‹ˆλ‹€. ν˜„μž¬ 초과 인좜 μƒνƒœμ˜ κ³„μ’Œμ—μ„œλŠ” λ‹€λ₯Έ μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μ—†μœΌλ©°, 이 사싀은 ν•˜μ΄νΌλ―Έλ””μ–΄μ— λ‚΄ν¬λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ›Ή λΈŒλΌμš°μ €λŠ” 초과 인좜 κ³„μ’Œμ˜ κ°œλ…μ΄λ‚˜ κ³„μ’Œκ°€ 무엇인지쑰차 μ•Œμ§€ λͺ»ν•©λ‹ˆλ‹€. 단지 ν•˜μ΄νΌλ―Έλ””μ–΄ ν‘œν˜„μ„ μ‚¬μš©μžμ—κ²Œ ν‘œμ‹œν•˜λŠ” λ°©λ²•λ§Œ μ•Œκ³  μžˆμ„ λΏμž…λ‹ˆλ‹€.

λ”°λΌμ„œ μš°λ¦¬λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μƒνƒœμ˜ μ—”μ§„μœΌλ‘œμ„œμ˜ ν•˜μ΄νΌλ―Έλ””μ–΄λΌλŠ” κ°œλ…μ„ κ°–κ²Œ λ©λ‹ˆλ‹€. λ¦¬μ†ŒμŠ€μ˜ μƒνƒœκ°€ 변함에 따라 κ°€λŠ₯ν•œ μž‘μ—…μ΄ 달라지며, 이 μ •λ³΄λŠ” ν•˜μ΄νΌλ―Έλ””μ–΄μ— μΈμ½”λ”©λ©λ‹ˆλ‹€.

μœ„μ˜ HTML 응닡과 일반적인 JSON APIλ₯Ό 비ꡐ해보면, JSON APIλŠ” λŒ€μ‹  κ³„μ’Œμ˜ μƒνƒœ ν•„λ“œλ₯Ό ν¬ν•¨ν•˜λŠ” ν‘œν˜„μ„ λ°˜ν™˜ν•  κ²ƒμž…λ‹ˆλ‹€:

HTTP/1.1 200 OK

{
    "account": {
        "account_number": 12345,
        "balance": {
            "currency": "usd",
            "value": -50.00
        },
        "status": "overdrawn"
    }
}

μ—¬κΈ°μ„œ ν΄λΌμ΄μ–ΈνŠΈλŠ” status ν•„λ“œμ˜ 값이 무엇을 μ˜λ―Έν•˜λŠ”μ§€, 이것이 μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€μ˜ λ Œλ”λ§μ— μ–΄λ–€ 영ν–₯을 미칠지에 λŒ€ν•΄ λͺ…ν™•νžˆ μ•Œκ³  μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. ν΄λΌμ΄μ–ΈνŠΈλŠ” λ˜ν•œ 응닡에 μΈμ½”λ”©λ˜μ§€ μ•Šμ€ λ¦¬μ†ŒμŠ€λ₯Ό μ‘°μž‘ν•˜κΈ° μœ„ν•œ URL도 μ•Œκ³  μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. μ΄λŠ” 일반적으둜 JSON API에 λŒ€ν•œ λ¬Έμ„œλ₯Ό μ°Έμ‘°ν•˜μ—¬ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

μ΄λ ‡κ²Œ μ™ΈλΆ€ 정보가 ν•„μš”ν•œ 점이 이 JSON APIλ₯Ό HATEOASλ₯Ό κ΅¬ν˜„ν•˜λŠ” RESTful API와 ꡬ뢄 μ§“λŠ” μš”μ†Œμž…λ‹ˆλ‹€.

이 μ˜ˆμ‹œλŠ” 두 가지 μ ‘κ·Ό λ°©μ‹μ˜ 핡심적인 차이λ₯Ό λ³΄μ—¬μ€λ‹ˆλ‹€: RESTful, HATEOAS HTML ν‘œν˜„μ—μ„œλŠ” λͺ¨λ“  μž‘μ—…μ΄ 응닡에 직접 μΈμ½”λ”©λ©λ‹ˆλ‹€. 반면 JSON API μ˜ˆμ‹œμ—μ„œλŠ” 원격 λ¦¬μ†ŒμŠ€λ₯Ό μ²˜λ¦¬ν•˜κ³  μž‘μ—…ν•˜λŠ” 데 μ™ΈλΆ€ 정보가 ν•„μš”ν•©λ‹ˆλ‹€.

#기원

HATEOAS μ œμ•½μ€ Roy Fielding의 박사 논문에 μ •μ˜λœ REST의 β€œν†΅μΌλœ μΈν„°νŽ˜μ΄μŠ€β€ κΈ°λŠ₯의 ν•„μˆ˜μ μΈ λΆ€λΆ„μž…λ‹ˆλ‹€. Fielding의 논문은 주둜 HTMLκ³Ό HTTP둜 κ΅¬μ„±λœ 초기 μ›Ή μ•„ν‚€ν…μ²˜μ— λŒ€ν•œ λ…Όμ˜μ˜€μŠ΅λ‹ˆλ‹€.

Fielding은 μžμ‹ μ˜ λΈ”λ‘œκ·Έμ—μ„œ ν•˜μ΄νΌλ―Έλ””μ–΄μ˜ κ°œλ…κ³Ό κ·Έ μ€‘μš”ν•œ μš”κ΅¬ 사항에 λŒ€ν•΄ 더 μžμ„Ένžˆ μ„€λͺ…ν–ˆμŠ΅λ‹ˆλ‹€.

#HATEOAS와 JSON

μ°Έκ³ : 이 μ„Ήμ…˜μ˜ 쀑립적인 μ–΄μ‘°λŠ” λ…ΌμŸμ˜ 여지가 μžˆμŠ΅λ‹ˆλ‹€.

2000λ…„λŒ€ μ΄ˆλ°˜μ— REST κ°œλ…μ€ 초기 μ›Ήμ˜ μ„€λͺ…μœΌλ‘œμ„œμ˜ κ°œλ…μ  ν™˜κ²½μ—μ„œ λ²—μ–΄λ‚˜ XML API 개발(주둜 SOAP을 μ‚¬μš©) 및 JSON API 개발과 같은 λ‹€λ₯Έ μ›Ή 개발 λΆ„μ•Όλ‘œ λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” XMLμ΄λ‚˜ JSON이 HTMLκ³Ό 같은 μžμ—°μŠ€λŸ¬μš΄ ν•˜μ΄νΌλ―Έλ””μ–΄κ°€ μ•„λ‹˜μ—λ„ λΆˆκ΅¬ν•˜κ³  μ΄λ£¨μ–΄μ‘ŒμŠ΅λ‹ˆλ‹€.

이 μƒˆλ‘œμš΄ μ˜μ—­μ—μ„œ REST에 λŒ€ν•œ μ€€μˆ˜ μˆ˜μ€€μ„ νŠΉμ§•μ§“κΈ° μœ„ν•΄ λ¦¬μ²˜λ“œμŠ¨ μ„±μˆ™λ„ λͺ¨λΈμ΄ μ œμ•ˆλ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이 λͺ¨λΈμ˜ κ°€μž₯ 높은 μˆ˜μ€€(Level 3)은 β€œν•˜μ΄νΌλ―Έλ””μ–΄ μ»¨νŠΈλ‘€β€œλ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€.

JSON은 μžμ—°μŠ€λŸ¬μš΄ ν•˜μ΄νΌλ―Έλ””μ–΄κ°€ μ•„λ‹ˆλ―€λ‘œ, ν•˜μ΄νΌλ―Έλ””μ–΄ κ°œλ…μ€ JSON μœ„μ— κ°•μ œλ‘œ 좔가될 μˆ˜λ°–μ— μ—†μŠ΅λ‹ˆλ‹€. λ¦¬μ²˜λ“œμŠ¨ μ„±μˆ™λ„ λͺ¨λΈμ˜ Level 3을 μΆ©μ‘±ν•˜λ €λŠ” JSON μ—”μ§€λ‹ˆμ–΄λŠ” μœ„μ˜ 은행 κ³„μ’Œ μ˜ˆμ‹œμ— ν•΄λ‹Ήν•˜λŠ” λ‹€μŒ JSON을 λ°˜ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

HTTP/1.1 200 OK

{
    "account": {
        "account_number": 12345,
        "balance": {
            "currency": "usd",
            "value": 100.00
        },
        "links": {
            "deposits": "/accounts/12345/deposits",
            "withdrawals": "/accounts/12345/withdrawals",
            "transfers": "/accounts/12345/transfers",
            "close-requests": "/accounts/12345/close-requests"
        }
    }
}

μ—¬κΈ°μ—μ„œ β€œν•˜μ΄νΌλ―Έλ””μ–΄ μ»¨νŠΈλ‘€β€œμ€ 계정 객체의 links 속성에 μΈμ½”λ”©λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 이 API의 ν΄λΌμ΄μ–ΈνŠΈλŠ” μ—¬μ „νžˆ μƒλ‹Ήν•œ μΆ”κ°€ 정보λ₯Ό μ•Œμ•„μ•Ό ν•©λ‹ˆλ‹€:

μœ„μ˜ JSON을 첫 번째 HTML μ˜ˆμ‹œμ— μžˆλŠ” /accounts/12345/deposits 링크λ₯Ό ν΄λ¦­ν•œ ν›„ λΈŒλΌμš°μ €μ—μ„œ κ°€μ Έμ˜¨ λ‹€μŒ HTTP 응닡과 비ꡐ해 λ³΄μ„Έμš”:

HTTP/1.1 200 OK

<html>
  <body>
    <form method="post" action="/accounts/12345/deposits">
        <input name="amount" type="number" />
        <button>Submit</button>
    </form>
  <body>
</html>

이 HTML 응닡은 κ³„μ’Œ μž”μ•‘μ„ μ—…λ°μ΄νŠΈν•˜λŠ” 데 ν•„μš”ν•œ λͺ¨λ“  정보λ₯Ό μΈμ½”λ”©ν•˜κ³  있으며, form μš”μ†Œμ— method와 action 속성이 ν¬ν•¨λ˜μ–΄ 있으며, λ¦¬μ†ŒμŠ€λ₯Ό μ˜¬λ°”λ₯΄κ²Œ μ—…λ°μ΄νŠΈν•˜λŠ” 데 ν•„μš”ν•œ μž…λ ₯ ν•„λ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

JSON ν‘œν˜„μ—λŠ” HTML ν‘œν˜„μ—μ„œμ™€ 같은 자체 ν¬ν•¨λœ β€œν†΅μΌλœ μΈν„°νŽ˜μ΄μŠ€β€œκ°€ μ—†μŠ΅λ‹ˆλ‹€.

JSON APIκ°€ RESTful κ°œλ…μ—μ„œ μ–Όλ§ˆλ‚˜ 멀리 λ²—μ–΄λ‚˜λ”λΌλ„, 이λ₯Ό ’REST’라고 λΆ€λ₯΄λŠ” 것에 λŒ€ν•΄ Roy Fielding은 λ‹€μŒκ³Ό 같이 λ§ν–ˆμŠ΅λ‹ˆλ‹€:

HTTP 기반의 λͺ¨λ“  μΈν„°νŽ˜μ΄μŠ€λ₯Ό REST API라고 λΆ€λ₯΄λŠ” μ‚¬λžŒλ“€μ΄ λ„ˆλ¬΄ λ§Žμ•„μ§€λ©΄μ„œ λ‚˜λŠ” 점점 μ’Œμ ˆν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 였늘의 μ˜ˆμ‹œλŠ” SocialSite REST APIμž…λ‹ˆλ‹€. 그것은 RPCμž…λ‹ˆλ‹€. μ™„μ „νžˆ RPC둜 λ³΄μž…λ‹ˆλ‹€. λ„ˆλ¬΄ λ§Žμ€ 결합이 λ“œλŸ¬λ‚˜ μžˆμ–΄μ„œ X 등급을 λ°›μ•„μ•Ό ν•  μ •λ„μž…λ‹ˆλ‹€.

JSON API에 더 λ³΅μž‘ν•œ ν•˜μ΄νΌλ―Έλ””μ–΄ μ»¨νŠΈλ‘€μ„ λ„μž…ν•˜λ €λŠ” μ‹œλ„κ°€ μžˆμ—ˆμ§€λ§Œ, μ „λ°˜μ μœΌλ‘œ μ—…κ³„λŠ” HATEOAS와 RESTful μ•„ν‚€ν…μ²˜μ˜ λ‹€λ₯Έ μš”μ†Œλ“€μ„ ν¬κΈ°ν•˜κ³  더 κ°„λ‹¨ν•œ RPC μŠ€νƒ€μΌμ˜ APIλ₯Ό μ„ ν˜Έν–ˆμŠ΅λ‹ˆλ‹€.

이 사싀은 HTMLκ³Ό 같은 μžμ—°μŠ€λŸ¬μš΄ ν•˜μ΄νΌλ―Έλ””μ–΄κ°€ RESTful μ‹œμŠ€ν…œμ„ κ΅¬μΆ•ν•˜λŠ” 데 μ‹€μ§ˆμ μœΌλ‘œ ν•„μˆ˜μ μ΄λΌλŠ” μ£Όμž₯을 κ°•λ ₯히 λ’·λ°›μΉ¨ν•©λ‹ˆλ‹€.

</>