본문으로 건너뛰기

REST와 RESTful

REST와 RESTful 내용 정리

개요

REST는 클라이언트와 서버 사이의 통신 방식이며, 자원을 이름으로 구분해 해당 자원의 상태(정보)를 주고 받는 모든 것을 의미한다.

2000년, 웹이 HTTP를 제대로 사용하지 못 하고 있는 상황을 보고 HTTP의 장점을 최대한 활용할 수 있는 아키텍처로서 REST가 소개되었다.

REST에는 몇 가지 기본 원칙이 있는데, 이 원칙을 성실히 지킨 서비스 디자인을 RESTful이라고 한다.

여기서 주의해야 할 것은, RESTful의 목적이 이해하기 쉽고 사용하기 쉬운 REST API를 만드는 것이지 성능 향상을 위한 것이 아니라는 것이다.

REST의 구성 요소

  • 자원(URI): 모든 자원에는 서버에 존재하는 고유한 ID가 있다. 이 ID를 URI라고 하며, 클라이언트에서 URI를 이용해 자원을 지정하고, 해당 자원의 상태에 대한 조작을 서버에 요청한다.
  • 행위(Method): HTTP 프로토콜의 메서드를 사용한다.
  • 표현: 클라이언트와 서버가 데이터를 주고 받는 형태(JSON, XML 등)

RESTful URI 설계 원칙

  1. 리소스를 명확히 표현하며, 동사를 포함하지 않는다. (예: /users)
  2. 리소스의 집합을 나타낼 때는 복수형을 사용한다.
  3. 리소스 간의 관계를 계층적으로 나타낸다. (예: /users/123/orders - ID 123 사용자의 주문 목록)
  4. 작업은 URI가 아닌 HTTP 메서드로 구분한다.

REST의 특징

  1. 상태 비저장성: 서버는 클라이언트의 상태를 저장해두지 않고, 요청마다 필요한 모든 정보를 포함해서 응답한다.
  2. 캐시 가능성: 클라이언트는 응답 데이터를 캐싱할 수 있어야 하며, 이를 통해 성능 최적화를 도모할 수 있다.
  3. 인터페이스 일관성: 인터페이스를 단순화화고 일관되게 만들어서 특정 언어에 종속되지 않게 한다.
  4. 자체 표현: 요청 메세지만 보고도 쉽게 이해할 수 있게 구성해야 한다.

RESTful API 예제

  • 리소스 조회: GET /users/123
  • 리소스 생성: POST /users
  • 리소스 수정: PUT /users/123
  • 리소스 삭제: DELETE /users/123
만약 리소스 조회하는데 POST 요청을 보낸다면 어떻게 될까?

서버가 POST 요청을 허용하도록 설계되어 있다면 POST 요청으로도 조회를 수행할 수 있다.

하지만 이렇게 하면 몇가지 문제점이 있다.

  1. RESTful 원칙 위반(구조상 문제)
  2. 캐싱 불가능(성능상 문제): GET 요청은 HTTP 프로토콜에 따라 캐싱 가능/POST 요청은 캐싱 불가

그럼에도, 일부 특정 상황에서는 POST 요청을 조회에 사용할 수 있다.

  1. 복잡한 필터링이나 조건이 필요할 경우: GET요청은 URL 길이에 제한이 있기에 이를 초과하면 POST를 사용할 수 있다. -> GET 요청이더라도 필터링 조건을 쿼리 파라미터로 전달함으로써 보완 가능
  2. 민감한 데이터 전달: URL에 포함된 데이터는 브라우저 기록이나 로그에 남을 수 있으므로 민감한 데이터를 본문에 포함한 POST 요청으로 전달하는 것이 더 안전할 수 있다. 하지만 이런 경우에는 민감한 데이터를 요청에 포함하기보다는 서버에서 세션이나 토큰을 사용해 식별하도록 설계하는 것이 좋다.

FE 개발자가 RESTful API를 위해 할 수 있는 것

  1. HTTP 메서드를 정확히 사용한다. (쏘 이지)
  2. 서버에서 반환하는 상태 코드를 적절히 해석하고, 이를 기반으로 UX를 개선할 수 있도록 한다.
  3. 캐싱을 적극 활용한다. (예: Cache-control 헤더, ETag 등을 활용)
  4. 서버가 제공하는 페이징, 정렬, 필터링 옵션을 사용해 효율적으로 데이터를 가져온다.
  5. 사용자 입력을 검증해 잘못된 데이터를 서버에 보내지 않도록 한다.
  6. 클라이언트 단위 테스트: API 호출이 예상된 응답을 반환하는지 테스트
  7. API 변경 사항 테스트: 서버가 업데이트될 경우 클라이언트와의 호환성을 테스트하여 충돌 방지

FE 개발자는 RESTful API 설계 자체를 담당하진 않더라도, RESTful API의 효과를 극대화하는 역할을 해야 한다. 이를 통해 UX를 향상시키고, 개발 효율성을 높이는 데 중요한 기여를 할 수 있다.

면접 질문과 답변

REST와 RESTful의 차이점은 무엇인가요?

REST는 웹 서비스를 설계하기 위한 아키텍처 스타일이고, RESTful은 REST의 원칙을 충실히 구현한 API를 말합니다.

REST의 원칙에는 리소스 기반 설계, HTTP 메서드 활용, 상태 비저장성, 캐싱 등의 원칙을 따릅니다.

RESTful API 설계에서 상태 비저장성을 유지하려면 어떻게 해야 하나요?

상태 비저장성을 유지하려면 각 요청이 독립적으로 처리되어야 합니다. 이를 위해 요청에 필요한 모든 정보를 포함해야 합니다.

클라이언트 개발자 입장에서는, 서버가 클라이언트 상태를 기억하지 않으므로 필요한 데이터를 매번 요청에 명확하게 포함해야 합니다.

RESTful API와 GraphQL의 차이점은 무엇인가요?

RESTful API와 GraphQL은 데이터 통신 방식에서 차이가 있습니다.

RESTful API는 HTTP 프로토콜과 긴밀하게 통합되며 간단하고 직관적이지만, Over-fetching과 Under-fetching 문제가 발생할 수 있습니다.

그에 비해 GraphQL은 쿼리를 통해 클라이언트가 필요한 데이터만 정확히 요청할 수 있고, 단일 엔드포인트로 다양한 요청을 처리할 수 있습니다. 하지만 RESTful API에 비해 설계와 구현이 복잡하고 캐싱 처리가 어렵다는 단점이 있습니다.

RESTful API의 단점을 보완하기 위해 사용할 수 있는 방법은 무엇인가요?

Over-fetching과 Under-fetching 문제를 보완하기 위해 GraphQL을 사용하거나 서버에서 필터링과 정렬 옵션을 제공할 수 있습니다.

또한 상태 비저장성으로 인해 클라이언트에서 상태 관리가 필요하므로 이를 보완하기 위해 상태 관리 라이브러리(Redux, Zustand 등)를 활용할 수 있습니다.

개인적인 궁금증

세션 기반 로그인은 비저장성을 위배하는데 RESTful 하지 않은가?

서버가 세션을 저장하고 클라이언트와의 상태를 유지하는 것은 상태 비저장성을 위반한다. 따라서 이 부분에서 RESTful 하지 않다고 볼 수 있다.

만약 서버가 상태를 저장하지 않고, 클라이언트가 세션 ID를 요청마다 전송한다면 상태 비저장성을 유지할 수 있다.

혹은, 세션을 로그인에만 사용하고 나머지 API 요청에서는 다른 인증 방식을 사용한다면 RESTful에 더 가까워질 수 있다. 예를 들어, 세션으로 한 번 인증한 후, 클라이언트가 JWT 토큰 같은 것을 발급받아 상태 비저장성을 유지할 수 있다.

왜 굳이 상태 비저장성을 지켜야 하는가?

서버에 상태를 저장해두는 것이 더 유리할 때가 꽤 있을 것 같은데, 왜 굳이 상태 비저장성을 지켜야 하는지가 의문이었다.

아무래도 서버 비용 문제가 제일 크지 않을까..? 아직은 조금 더 고민을 해봐야 할 것 같다.

한 API가 너무 많은 정보를 반환할 때 발생하는 문제를 어떻게 해결하면 좋을까?

베팅덕 프로젝트를 진행하며, 베팅룸 정보 조회 API를 굉장히 많은 곳에서 호출했다.

베팅룸 정보 조회 API는 다음과 같은 정보를 반환한다.

{
"status": 200,
"data": {
"channel": {
"id": "12345",
"title": "기아 vs 삼성 승부 예측",
"creator": {
"id": "host123",
},
"options": {
"option1": {
"name": "기아",
},
"option2": {
"name": "삼성",
}
},
"status": "waiting", // waiting, active, timeover, finished
"settings": {
"defaultBetAmount": 100, // 최소 베팅 금액
"duration": 120 // 초 단위
},
"metadata": {
"createdAt": "2024-11-10T10:00:00Z",
"startAt": null,
"endAt": null
},
"urls": {
"invite": "http://example.com/room/12345"
}
"isAdmin": "true" // true, false
// "isPlaceBet": "true" // true, false
// "bettingAmount": "300"
},
"message": "OK"
}
}

정보가 너무 많은 문제(Over-fetching)

대체로 내가 필요한 정보는 title, options, status, setting 정보가 전부인데, 너무 많은 정보를 반환하고 있다. 이렇게 과도하게 정보가 오는 걸 over-fetching 문제라고 한다.

이 문제를 어떻게 해결할 수 있을지 고민하는 과정에서 백엔드 팀원과 상의를 해봤다. RESTful API 방식을 유지한다는 가정 하에 해결 방법은 크게 두 가지가 있다.

  1. 서버에서 필터링 기능을 제공하고, 클라이언트에서 요청을 보낼 때 필터링 기반 요청을 보낸다.
  2. 서버에서 요약 정보를 보내주는 기본 API와 상세 정보를 주는 API를 분리한다.

이 두 가지 방법 중 두 번째를 선택하기로 했다. 두 번째 방법에서는 클라이언트가 특정 요청마다 필드를 지정할 필요가 없어 편리하고 유지보수가 쉬워질 것이라고 판단했기 때문이다.

API를 좀 더 fit하게 설계하면 편리하겠으나, 그렇게 구현한다면 일회용 API가 되어버린다. 이는 재사용성 측면에서 문제가 있다. 반대로 과도하게 일반화 하면 지금처럼 over-fetching 문제가 발생하므로 이 둘 사이의 균형을 맞추는 것이 중요하다. 백엔드 팀원분과 이런 균형점에 대해 더 논의해봐야 할 듯 하다.

특정 상황에서는 GraphQL을 도입하는 것도 하나의 해결 방안이 될 수 있겠다고 생각했지만, 여러 방식을 혼용하는 것보다는 REST 원칙을 따르되, 상세 정보와 요약 정보를 분리하는 것이 좋을 것 같다.

똑같은 정보를 받아오려고 너무 많은 요청을 보내고 있다.

베팅룸 상세 조회 API 요청에 대한 응답이 여러 페이지에서 중복으로 사용되고 있음에도, 내가 짠 코드에서는 매번 새로운 요청을 보내고 있다. 이는 서버 자원을 불필요하게 낭비하는 일이므로, 클라이언트 캐싱을 활용해 개선할 계획이다. 현재 React Query나 로컬 스토리지를 활용한 캐싱 방법을 학습 중이다.