REST는 자원(Resource), HTTP 메서드, **표현(Representation)**의 세 가지 주요 요소로 구성됩니다.
1) 자원(Resource):
- REST는 모든 것을 자원으로 간주합니다.
- 자원은 고유한 식별자(URI)로 접근됩니다.
- 예: https://api.example.com/users는 사용자 목록 자원.
2) HTTP 메서드:
REST API는 HTTP 프로토콜의 메서드를 활용하여 자원에 대한 동작을 정의합니다.
- GET: 자원을 조회.
- POST: 자원을 생성.
- PUT: 자원을 업데이트 (전체 수정).
- PATCH: 자원을 부분적으로 수정.
- DELETE: 자원을 삭제.
3) 표현(Representation):
- 자원의 상태를 표현하는 데이터 형식.
- JSON, XML, HTML 등이 사용되며, 대부분의 REST API는 JSON을 기본으로 사용.
2. REST의 설계 원칙
REST API는 다음의 설계 원칙을 준수해야 합니다:
- 클라이언트-서버 구조:
- 클라이언트는 요청을 보내고 서버는 응답을 처리.
- 클라이언트와 서버는 독립적으로 개발 및 배포 가능.
- 무상태성 (Stateless):
- 서버는 클라이언트의 상태를 유지하지 않음.
- 각 요청은 필요한 모든 정보를 포함해야 함.
- 캐시 가능(Cacheable):
- 응답 데이터는 캐싱 가능하도록 설계할 수 있음.
- 이를 통해 성능을 향상하고 서버 부하를 줄임.
- 일관된 인터페이스(Uniform Interface):
- URI 설계와 HTTP 메서드 사용이 명확하고 일관되어야 함.
- 계층적 시스템(Layered System):
- API 서버는 중간 계층(프록시, 로드 밸런서 등)을 통해 확장 가능.
- 코드 온 디맨드 (선택 사항):
- 필요 시 서버가 클라이언트에 스크립트나 코드를 전달하여 실행 가능.
3. REST API의 동작 방식
REST API는 클라이언트-서버 간 HTTP 요청과 응답을 통해 동작합니다.
요청(Request):
- HTTP 메서드: 클라이언트가 수행할 동작 지정.
- URI: 작업 대상 자원 지정.
- 헤더(Header): 요청과 응답의 메타데이터.
- 본문(Body): 요청 시 필요한 데이터 포함 (POST, PUT 등).
응답(Response):
- 상태 코드(Status Code): 요청 결과를 나타냄.
- 200: 성공
- 201: 자원 생성 성공
- 400: 잘못된 요청
- 404: 자원 없음
- 500: 서버 오류
- 헤더(Header): 응답 메타데이터.
- 본문(Body): 요청 결과 데이터 포함.
4. REST API 예제
1) 사용자 목록 조회:
요청: GET /users HTTP/1.1 Host: api.example.com
응답: [ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ]
2) 사용자 생성:
요청: POST /users HTTP/1.1 Host: api.example.com Content-Type: application/json { "name": "Charlie" }
응답: HTTP/1.1 201 Created Location: /users/3
5. REST API의 장점
- 확장성: 서버와 클라이언트의 독립적인 설계.
- 표준화: HTTP 메서드와 상태 코드를 사용하여 직관적.
- 유연성: 다양한 데이터 형식(JSON, XML 등)을 지원.
- 캐싱 가능: 성능 최적화 가능.
6. REST API의 한계
- 복잡한 연산: 자원 간 복잡한 관계를 표현하기 어렵다.
- HTTP 의존: REST는 HTTP에 강하게 의존하므로, 다른 프로토콜과는 호환이 제한적.
- 무상태성: 클라이언트의 상태를 유지해야 하는 애플리케이션에서는 부담이 될 수 있음.
1. 복잡한 연산: 자원 간 복잡한 관계 표현의 어려움
- REST는 **자원(Resource)**을 중심으로 설계되기 때문에, 자원을 명확히 정의하고 그 자원들 간의 관계를 표현하는 방식에 제한이 있습니다.
- 예를 들어, 복잡한 비즈니스 로직을 처리해야 할 때, REST는 요청과 응답 간의 단순한 CRUD(생성, 읽기, 업데이트, 삭제) 작업에 초점을 맞추기 때문에, 자원 간의 복잡한 관계를 효율적으로 표현하기 어렵습니다.
예시:
- 예를 들어, 쇼핑몰 애플리케이션에서 "주문" 자원이 "상품", "고객", "배송지" 등 다양한 자원들과 관계를 맺고 있다고 가정해봅시다. 복잡한 주문 생성이나 상태 업데이트가 필요할 경우, REST API에서는 각 자원을 독립적으로 다루며, 여러 API 호출을 통해 관계를 업데이트해야 할 수 있습니다. 이 경우, 비즈니스 로직을 클라이언트와 서버가 각각 나누어서 처리해야 하므로, 코드가 더 복잡해지고 클라이언트와 서버 간의 불필요한 통신이 증가할 수 있습니다.
해결 방법:
- 이런 복잡한 연산을 처리하려면 GraphQL이나 gRPC와 같은 대안이 유리할 수 있습니다. 예를 들어, GraphQL은 단일 쿼리로 여러 자원에 대한 복잡한 관계를 요청하고 처리할 수 있어, REST보다 더 복잡한 연산을 간단히 처리할 수 있습니다.
2. HTTP 의존성: 다른 프로토콜과의 호환성 제한
- REST는 HTTP 프로토콜을 기반으로 작동하는 아키텍처 스타일입니다. 이는 웹 기반 애플리케이션에서 매우 강력하지만, 다른 프로토콜을 사용하는 시스템과의 호환성에는 한계가 있습니다. REST API는 기본적으로 HTTP 메서드(GET, POST, PUT, DELETE 등)와 상태 코드(200, 404, 500 등)에 의존하기 때문에, HTTP 외의 프로토콜에서는 직접적으로 사용할 수 없습니다.
예시:
- 예를 들어, 메시징 시스템에서 AMQP나 MQTT와 같은 프로토콜을 사용할 때, REST API를 그대로 사용할 수 없습니다. HTTP와는 다른 프로토콜 특성상, 메시징 시스템에서는 비동기적이고, 지속적인 연결을 요구하기 때문에 REST API로 표현하기 어렵습니다.
해결 방법:
- 이러한 한계를 해결하려면 gRPC나 WebSockets와 같은 다른 프로토콜을 고려해야 합니다. gRPC는 HTTP/2 기반으로, 양방향 스트리밍과 빠른 속도를 제공하며, RPC(원격 프로시저 호출) 방식으로 다양한 프로토콜을 사용할 수 있습니다.
3. 무상태성: 클라이언트 상태 관리의 부담
- REST API는 **무상태성(stateless)**을 기본 원칙으로 합니다. 즉, 각 요청은 서버에서 독립적으로 처리되며, 요청 간에 서버 상태를 유지하지 않습니다. 클라이언트가 상태 정보를 계속해서 전달해야 하므로, 클라이언트에서 상태를 관리해야 할 부담이 있을 수 있습니다.
예시:
- 예를 들어, 사용자 인증과 같은 작업을 처리할 때, REST API는 각 요청에서 인증 토큰(JWT 등)을 요구할 수 있습니다. 클라이언트가 여러 번의 요청을 보낼 때마다 인증 토큰을 포함시켜야 하므로, 상태를 서버가 아닌 클라이언트에서 관리해야 합니다. 이는 클라이언트 측에서 상태를 추적하고 관리하는 데 추가적인 작업이 필요할 수 있습니다.
- 또 다른 예시는 장바구니와 같은 상태를 관리하는 애플리케이션에서 REST를 사용하면, 장바구니 상태를 매번 클라이언트에서 서버로 전달해야 하는 번거로움이 있습니다.
해결 방법:
- 세션 관리나 상태 저장소(예: Redis)와 같은 기술을 이용하여 서버에서 상태를 관리하는 방법도 있습니다. 또한, **JWT(JSON Web Token)**를 활용하여 클라이언트가 상태를 관리하고, 그 정보를 서버에서 검증하는 방법도 있습니다. 하지만 상태를 서버에서 관리하면 상태를 저장하는 서버 자원이 필요하므로, 시스템의 복잡도나 확장성 측면에서 고려해야 할 부분이 많습니다.