HATEOAS, REST의 잊혀진 영광을 인정하다
Takashi Yamamoto
Infrastructure Engineer · Leapcell

소개
끊임없이 발전하는 백엔드 개발 환경에서 RESTful API는 확장 가능하고 유지보수 가능한 웹 서비스를 구축하는 사실상의 표준으로 오랫동안 자리 잡아 왔습니다. 그러나 애플리케이션이 복잡해짐에 따라 REST의 미묘하지만 심오한 측면이 종종 간과되거나 심지어 명시적으로 기각되기도 합니다. 바로 HATEOAS(Hypermedia As The Engine Of Application State)입니다. REST의 가장 강력하지만 가장 덜 채택된 원칙이라고 할 수 있는 이 중요한 제약 조건은 진정으로 분리되고 자체 설명적인 API 경험을 제공할 것을 약속합니다. 그러나 OpenAPI 사양과 클라이언트 주도 개발이 지배하는 시대에 HATEOAS는 여전히 관련성 있는 아키텍처 선택일까요, 아니면 단순히 이론적인 이상, 원래 REST 비전의 잊혀진 영광이 되었을까요? 이 글은 HATEOAS의 본질, 실질적인 영향 및 현대 API 디자인에서의 잠재적인 부활을 탐구하고 궁극적으로 그것의 시간이 왔는지, 아니면 영원히 시대를 앞서 나가는 상태로 남아 있는지 묻습니다.
HATEOAS, REST의 잊혀진 영광
HATEOAS에 대한 논의를 완전히 이해하려면 먼저 몇 가지 기본 개념을 명확히 해야 합니다.
REST (Representational State Transfer): 네트워킹된 하이퍼미디어 애플리케이션을 위한 아키텍처 스타일입니다. 성능, 확장성, 단순성, 수정 가능성, 가시성, 이식성 및 안정성과 같은 바람직한 속성을 가진 분산 시스템을 생성하는 제약 조건 세트를 정의합니다. 주요 제약 조건에는 클라이언트-서버, 무상태, 캐시 가능, 계층형 시스템 및 통합 인터페이스가 포함됩니다.
통합 인터페이스: 이것은 우리의 논의에서 가장 중요한 REST 제약 조건입니다. 클라이언트가 API를 상호 작용하는 통일되고 일반적인 방법이 있어야 함을 규정합니다. 네 가지 하위 제약 조건이 이을 더 정의합니다.
- 리소스 식별: 리소스는 URI로 식별됩니다.
- 표현을 통한 리소스 조작: 클라이언트는 표현을 교환하여 리소스를 조작합니다.
- 자체 설명 메시지: 클라이언트와 서버 간에 교환되는 각 메시지에는 처리 방법을 설명하기에 충분한 정보가 포함됩니다.
- 애플리케이션 상태를 위한 하이퍼미디어(HATEOAS): 서버는 표현 내에 링크를 제공하여 클라이언트에게 사용 가능한 작업과 상태 전환을 안내함으로써 하이퍼미디어를 통해 애플리케이션 상태를 전환합니다.
HATEOAS 설명
HATEOAS는 클라이언트가 초기 진입점 외에는 API와 상호 작용하는 방법에 대한 사전 지식이 필요 없음을 규정합니다. 대신 서버는 응답 내에 링크를 제공하여 클라이언트에게 현재 사용 가능한 작업을 수행하는 방법과 수행 방법을 안내합니다. 이러한 링크는 제어 메커니즘 역할을 하여 API가 리소스 표현이 일관성을 유지하는 한 클라이언트를 중단시키지 않고 발전할 수 있도록 합니다.
전자상거래 주문을 예로 들어 보겠습니다.
기존 REST 응답(HATEOAS 없음):
{ "orderId": "12345", "status": "pending", "totalAmount": 99.99, "customerId": "C001" }
이 주문을 업데이트하려면 클라이언트는 PUT /orders/{orderId} 엔드포인트가 존재하고 특정 페이로드가 필요하다는 것을 알아야 합니다. 취소를 위해서는 DELETE /orders/{orderId}를 알 수 있습니다. 이러한 결합은 API가 엔드포인트 구조나 사용 가능한 작업을 변경하면(예: "pending" 주문은 특정 시간 이후에만 "cancelled"될 수 있고 "updated"될 수 없음) 클라이언트 코드가 중단된다는 것을 의미합니다.
HATEOAS 기반 REST 응답:
{ "orderId": "12345", "status": "pending", "totalAmount": 99.99, "customerId": "C001", "_links": { "self": { "href": "/orders/12345", "method": "GET" }, "update": { "href": "/orders/12345", "method": "PUT", "type": "application/json" }, "cancel": { "href": "/orders/12345/cancel", "method": "POST" }, "customer": { "href": "/customers/C001", "method": "GET" } } }
이 HATEOAS 예에서 _links 객체는 클라이언트에게 현재 주문 상태에서 가능한 작업을 명시적으로 알려줍니다. 주문 상태가 "shipped"로 변경되면 서버는 "track" 또는 "return" 작업에 대한 링크를 제공할 수 있지만 더 이상 "update" 또는 "cancel"에 대한 링크는 제공하지 않습니다. 클라이언트는 단순히 링크 관계(예: "update", "cancel")를 이해하고 제공된 URI와 메서드를 따르면 됩니다.
프레임워크에서의 구현
많은 최신 백엔드 프레임워크는 HATEOAS를 위한 라이브러리 또는 내장 지원을 제공합니다.
Java (Spring HATEOAS):
Spring HATEOAS는 Spring Boot 애플리케이션을 위한 HATEOAS 구현을 단순화하는 인기 있는 라이브러리입니다.
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/orders") public class OrderController { @GetMapping("/{id}") public ResponseEntity<EntityModel<Order>> getOrder(@PathVariable String id) { Order order = findOrderById(id); // Assume this fetches the order from a service EntityModel<Order> orderModel = EntityModel.of(order); orderModel.add(linkTo(methodOn(OrderController.class).getOrder(id)).withSelfRel()); if ("pending".equals(order.getStatus())) { orderModel.add(linkTo(methodOn(OrderController.class).updateOrder(id, null)).withRel("update")); orderModel.add(linkTo(methodOn(OrderController.class).cancelOrder(id)).withRel("cancel")); } else if ("shipped".equals(order.getStatus())) { orderModel.add(linkTo(methodOn(OrderController.class).trackOrder(id)).withRel("track")); } orderModel.add(linkTo(methodOn(CustomerController.class).getCustomer(order.getCustomerId())).withRel("customer")); return ResponseEntity.ok(orderModel); } // Other methods like updateOrder, cancelOrder, trackOrder with @PostMapping or @PutMapping // ... ... }
이 Spring 예에서는 EntityModel.of(order)가 Order 객체를 래핑한 다음 add()를 사용하여 WebMvcLinkBuilder를 사용하여 만든 Link 객체를 편리하게 연결합니다. linkTo와 methodOn은 컨트롤러 메서드를 기반으로 URI를 생성하는 강력한 도구입니다.
Node.js (HATEOAS 헬퍼 라이브러리를 포함한 Express):
Node.js는 Spring HATEOAS와 같은 직접적인 동급은 없지만 헬퍼 라이브러리를 생성하거나 채택할 수 있습니다.
// order.routes.js const express = require('express'); const router = express.Router(); function addHateoasLinks(order) { const links = { self: { href: `/orders/${order.orderId}`, method: 'GET' }, customer: { href: `/customers/${order.customerId}`, method: 'GET' } }; if (order.status === 'pending') { links.update = { href: `/orders/${order.orderId}`, method: 'PUT', type: 'application/json' }; links.cancel = { href: `/orders/${order.orderId}/cancel`, method: 'POST' }; } else if (order.status === 'shipped') { links.track = { href: `/orders/${order.orderId}/track`, method: 'GET' }; } return { ...order, _links: links }; } router.get('/:id', (req, res) => { const orderId = req.params.id; const order = findOrderById(orderId); // Assume this fetches the order if (!order) { return res.status(404).send('Order not found'); } res.json(addHateoasLinks(order)); }); module.exports = router;
이 Node.js 예는 종종 유틸리티 함수 또는 미들웨어에 캡슐화된 HATEOAS 링크를 주입하는 수동 접근 방식을 보여줍니다.
애플리케이션 시나리오
HATEOAS는 특정 시나리오에서 빛을 발합니다.
- API 진화 및 장기 유지보수: API 기능이 자주 변경되거나 새 기능이 추가될 것으로 예상되는 경우 HATEOAS를 사용하면 모든 엔드포인트 변경에 대한 코드 변경 없이 클라이언트를 조정할 수 있습니다.
- 범용 클라이언트: 모든 엔드포인트 지식을 구체적으로 갖는 것 없이 단순히 하이퍼미디어 컨트롤을 따름으로써 특정 리소스 유형을 노출하는 모든 API와 상호 작용할 수 있는 진정한 범용 클라이언트를 구축합니다.
- 사람이 읽을 수 있는 API: 클라이언트는 일반적으로 프로그램이지만 HATEOAS의 자체 설명적인 특성은 인간 개발자가 API를 탐색하고 이해하는 데 더 쉽게 만들 수도 있습니다.
- 클라이언트 및 서버 분리: 클라이언트와 서버 간의 긴밀한 결합을 줄여 각자의 독립적인 배포 및 버전을 허용합니다.
"잊혀졌다"고 하는 이유는 무엇입니까?
건축학적 이점에도 불구하고 HATEOAS는 어려움에 직면합니다.
- 간단한 API에 대한 복잡성: 간단한 CRUD API의 경우 링크를 생성하고 구문 분석하는 오버헤드는 불필요해 보일 수 있습니다.
- 클라이언트 측 "무시": 많은 클라이언트는 어떤 엔드포인트를 호출해야 하는지 정확히 알도록 설계되었습니다. 개발자는 동적 링크 검색보다는 명시적인 API 문서(OpenAPI 등)를 우선시하는 경우가 많습니다.
- 도구 부족: 일부 프레임워크는 지원을 제공하지만, 기존 REST에 비해 언어에 구애받지 않는 강력한 클라이언트 측 HATEOAS 파서 및 SDK 생성기는 일반적이지 않습니다.
- 상태 혼란: HATEOAS가 무상태와 어떻게 관련되는지에 대한 오해. HATEOAS는 서버 측 세션 상태가 아닌 하이퍼미디어를 통해 애플리케이션 상태 전환을 관리합니다.
- 개발자 사고방식: 널리 퍼진 개발 패러다임은 종종 명시적인 계약 및 조기 바인딩을 선호하며, 이는 HATEOAS에 의해 도전받습니다.
결론
HATEOAS는 REST 아키텍처 스타일의 초석이지만, 주류 API 디자인에서는 대부분 구현되지 않은 이상으로 남아 있습니다. API 검색 가능성, 진화 가능성 및 진정한 클라이언트-서버 분리 측면에서 부인할 수 없는 이점을 제공하여 독립적인 진화가 가장 중요한 오래 지속되고 복잡한 시스템에 강력한 도구가 됩니다. 그러나 인식된 복잡성, OpenAPI와 같은 대안적 문서의 만연, 그리고 명시적인 계약에 대한 일반적인 개발자 선호도는 채택을 어렵게 만들었습니다. 모든 API에 HATEOAS의 완전한 힘이 필요한 것은 아니지만, 진정으로 탄력 있고 적응 가능한 아키텍처를 추구하는 API에게는 HATEOAS를 수용하는 것이 API를 정적 계약에서 동적이고 자체 탐색 가능한 환경으로 변화시켜 현대 API 디자인에서 강력하고 관련성이 있지만 종종 간과되는 원칙으로 남아 있음을 증명할 수 있습니다.

