REST API

JaeGu Jeong·2024년 2월 15일
0

개발 도구들

목록 보기
2/12

구분

Representational State Transfer Application Programming Interface
웹 통신을 위한 아키텍처 스타일이에요.

RESTful하게 개발해야하는 이유

협업 생산성과 성능을 동시에 챙길 수 있어요. 간결성과 독립성 그리고 시퀀스 비용을 우선순위로 개발하면 가독성과 확장성이 좋아지고 유지보수하기 편해져요.
특히 HATEOAS를 사용하면 프론트엔드 수정 없이 동적으로 버전 업데이트가 가능해요.

잃어버리는 단점

좋은 아키텍처이지만 대신 잃어버리는 단점도 있어요.

1. 트렌젝션 최소화 제한.

자원마다 트랜젝션을 별도로 호출하기 때문에 하나의 웹 페이지에서 호출하는 API수에 비례해서 트렌젝션이 늘어나요.
캐싱으로 어느정도 커버가 가능하지만 분명한 한계가 있어요.

2. 트래픽 상승.

대표적으로 HATEOAS를 사용하면 상태 전이가 쉬운 장점도 있지만 그만큼 response에 들어가는 페이로드도 증가해요.
예를 들어 10개의 엔드포인트를 가진 자원을 10만명이 호출하면 100만개의 주소 값이 트래픽에 들어가요.
실제로 카카오같은 큰 트래픽을 다루는 대기업들은 HATEOAS를 넣지 않고 설계하기도 해요.

3. 실시간 인가 변경 어려움.

유저를 실시간으로 관리해야 할 때 Session을 사용하면 무상태를 위배하게 돼요.
그래서 로그인 인가가 필요한 서비스는 JWT를 사용하게 돼요.
로그인 정보가 담긴 JWT는 일반적으로 재발급 할 때 까지 값이 변경되지 않아요.

결론

결국 서비스 요구사항과 성능 최적화가 우선이에요.
요구사항을 위해서 완벽한 REST API를 포기할 수도 있기에 RESTful API라고도 불러요.

특징

I. 무상태 / Stateless.

서버와 클라이언트간에 독립성을 가지고 이전 통신을 기억하지 않아요.

예시로 식당 서빙하는 사람을 '서버'로 손님을 '클라이언트'로 비유하면 아래와 같아요.

Stateless (무상태) : 떡볶이 1인분, 순대 1인분 주세요!

Stateful (상태 유지) : 늘 먹던걸로!

II. 자원 / Resources.

  1. 자원은 고유한 uri를 가져요.
  2. 정의 방식은 강제하지는 않지만 일반적인 규칙이 있어요.

    명사 > 동사 && 복수표기 > 단수 && 하이픈 > 언더바

  3. uri를 정의하는 순서는 왼쪽부터 범위를 내림차 순으로 정의해요.

    outside -> inside

    example.com서버의 A번째 방의 B번째 컴퓨터의 C번째 저장소

    example.com/rooms/{room_num}/pcs/{pc_num}/repositories/{repo_num}

III. 표현 / Representation.

API를 직관적으로 이해하고 처리할 수 있어야 해요.

JSON 또는 XML로 반환.

이유는 표준 형식으로 플랫폼간 호환성이 좋고 key-value형식으로 가독성이 높아 유지보수에 유리해요.

IV. 균일 / Uniform Interface.

서버와 사용자가 서로 스트레스를 줄이기 위한 약속이에요.
다음 4가지 조건을 요구 해요.

1. 자원식별 / Identification of Resources.

자원은 고유한 uri로 식별되어야 해요.
다음은 RPG게임에서 아이템A를 다루는 uri 사용 예시에요.

자원식별 예시: 'A조각'을 줍기, 'A조각'을 제작하기, 'A조각'을 가공하기, 'A조각'을 버리기.

GET /items/a
POST /items/a
PATCH /items/a
DELETE /items/a

잘못된 예시: 'A조각줍기'하기, '제작기능A'하기, 'A를B변경'하기, '제거A'하기.

GET /get_item_a
POST /item/creat_a
PATCH /convert/a_to_b
DELETE /remove/a

2. 메서드로 자원조작 / Manipulation of Resources Through Representations.

메서드로 자원 조작이 가능해야 해요.

조작 예시:

GET /items/a
POST /items/a
PATCH /items/a
DELETE /items/a

잘못된 조작:

POST /items/a/get
GET /items/a/post
GET /items/a/update
POST /items/a/delete

3. 자원 사용법 설명 / Self-Descriptive Messages

요청과 응답에 충분한 정보가 담겨야 해요. 대표적으로 클라이언트는
json인지 xml인지 html인지 원하는 자원의 타입을 알려줘야 해요.

GET /users/13 HTTP/1.1
Host: example.com
Accept: application/json << 이렇게

서버도 응답에 자원의 타입과 상태코드 등등 자원에 대한 정보를 충분히 제공해요.
자세한 response 예시는 포스트 하단에 "그 외 /HTTP response"를 참고해 주세요.

HTTP/1.1 200 OK
Content-Type: application/json << 이렇게

{
  "id": 13,
  "name": "Jay Jeong",
  "email": "Jay13@example.com"
}

4. 상호 작용 방법 제시 / Hypermedia as the Engine of Application State.

처음에 언급한 "HATEOAS"에요. (발음 : https://www.youtube.com/watch?v=8xNQhDR8yRw)
API가 반환하는 JSON값에 가져온 자원으로 무엇을 할 수 있는지 알려줘요.
다음은 '/api/users/13' 요청의 반환 값이에요. "_links"로 묶여 있어요.

 {
  "user": {
    "id": 13,
    "name": "Jay Jeong",
    "_links": {
      "self": {
        "href": "/api/users/13"
      },
      "profile": {
        "href": "/api/users/13/profile"
      },
      "posts": {
        "href": "/api/users/13/posts"
      }
    }
  }
}

V. 캐싱 / Caching.

API는 캐싱이 가능해야 해요.
클라이언트와 서버의 반복 작업을 최소화 하기 위해 사용해요.
주로 Redis같은 메모리 저장소나 304와 412 상태코드를 사용하는 방식이 있어요.
헤더를 사용한 캐싱 방법은 포스트 하단에 "캐싱할 때 사용하는 헤더"를 봐주세요.

VI. 계층 구조 / Layered System.

계층 구조를 허용 해요.
요청부터 자원까지 도달하는 과정에서 모든 계층은 독립적으로 개발해야 해요.
구체적인 예시로 API를 개발할 때 "웹서버, WAS, ORM, DB"를 확장성을 고려하면서 개발해야 해요.

그 외

HTTP response

REST API에 대한 직접적인 내용은 아니지만 응답 방식에 대한 내용이에요.
각 메서드 별로 response가 다르게 표현 돼요.
각 응답을 다음 작업에 사용해도 되고 필요에 맞게 사용하지 않아도 돼요.

GET, PATCH

HTTP/1.1 200 OK
Content-Type: application/json

{
  "user": {
    "id": 13,
    "name": "Jay Jeong",
    "_links": {
      "self": {
        "href": "/api/users/13"
      },
      "profile": {
        "href": "/api/users/13/profile"
      },
      "posts": {
        "href": "/api/users/13/posts"
      }
    }
  },
  "message": "User details processed successfully."
}

POST

HTTP/1.1 201 Created
Content-Type: application/json

 {
  "user": {
    "id": 13,
    "name": "Jay Jeong",
    "_links": {
      "self": {
        "href": "/api/users/13"
      },
      "profile": {
        "href": "/api/users/13/profile"
      },
      "posts": {
        "href": "/api/users/13/posts"
      }
    }
  },
  "message": "User created successfully."
}

DELETE, PATCH

삭제의 응답은 response body가 없어요.
PATCH도 업데이트 된 자원 확인이 필요없다면 선택적으로 업데이트의 응답도 204처리 해요.

HTTP/1.1 204 No Content

캐싱할 때 사용하는 헤더

서버 side

  1. response에 'Last-Modified'헤더로 클라이언트에게 마지막 자원 변경시점을 알려줘요. 클라이언트는 다음 요청 때 If-Modified-Since헤더에 사용해요.

HTTP/1.1 200 OK
Content-Type: text/plain
Last-Modified: Wed, 09 Feb 2024 14:00:00 GMT << 이렇게

Hello, this is the content of example.txt.

  1. Cashe-Control로 캐싱 유효기간을 설정할 수 있어요.
    HTTP 1.1이상에서는 캐싱이 필요하면 'Cashe-Control' 헤더를 권장해요.
    자원의 유효기간 타이머를 설정하고 중간 캐시서버에 재사용 가능여부를 지시할 수 있어요. (Nginx 프록시 캐시 서버)

Cache-Control: max-age=3600, public << 중간 캐시 서버가 캐싱 해서 다른 클라이언트에게 공유한다.
또는
Cache-Control: max-age=3600, private << 이렇게하면 다른 클라이언트는 새로 받아야 한다.

  1. 클라이언트에게 'Expires'헤더로 캐싱 유효기간을 부여 해요.
    현재는 Cashe-Control을 지원하지 않는 경우를 대비해서 사용 해요.
    캐싱을 원하지 않으면 Cashe-Control설정과 별개로 "0"으로 설정해서 사용 해요.
    "Expires: 0"는 epoch이라는 리눅스의 최소 날짜에요.
    만료 날짜가 보통 1970년으로 설정 돼요.
    동시에 사용하면 현대 브라우저는 Cashe-Control이 우선적으로 적용 돼요.

HTTP/1.1 200 OK
Expires: Wed, 13 Feb 2024 15:30:00 GMT << 이렇게
Content-Length: 27
Content-Type: text/plain;charset=UTF-8
Date: Wed, 13 Feb 2024 14:30:00 GMT

This is a protected resource.

  1. 'must-revalidate' 헤더로 금융서비스나 실시간서비스는 상황에 따라 캐시된 데이터라도 검증을 강요할 수 있어요.
    브라우저와 중간 캐시 서버마다 캐싱 규칙이 다르기 때문에 별도의 캐싱 설정은 클라이언트가 하지 않아도 실시간 서버와 불일치된 캐싱된 데이터를 사용하는 경우를 대비해서 사용 해요.

Cache-Control: public, max-age=3600, must-revalidate

  1. 'If-None-Match' 헤더
    'ETag' 헤더 해시를 보내 불일치하면 304를 반환하게 해요.
    ETag는 이미지나 동영상과 같이 큰 자원을 해시한 값이에요.
    대용량 자원은 블록 단위로 나눠서 해싱 해요.
    murmurhash같은 간단한 방식으로 빠르게 해시해서 식별자로 사용하고
    해시가 중복되면 sha256으로 검사하면 성능과 일관성을 지킬 수 있어요.

클라이언트 side

  1. 클라이언트에 캐싱된 API를 재 호출할 때 서버에게 마지막 수정시간을 보내요.
    서버는 수정 시간을 비교해서 리소스 대신 "304 Not Modified"를 반환하게 돼요.

GET /example/resource HTTP/1.1
Host: example.com
If-Modified-Since: Mon, 01 Feb 2024 12:00:00 GMT << 이렇게

  1. 'If-Unmodified-Since' 헤더
    클라이언트가 캐싱된 자원으로 삭제나 업데이트를 진행할 때 마지막 수정 시간을 보내서
    서버가 수정 시간을 비교해서 오래된 캐싱 자원으로 요청했다면 412를 반환하게 돼요.
profile
BackEnd Developer

0개의 댓글