13주차: 웹 API 디자인

김민지·2024년 8월 28일

REST 살펴보기

API 디자인과 REST

  • REST (REpresentational State Transfer) : 표현된 자원의 상태를 주고받는 방법을 정리한 아키텍처 스타일
    web API 디자인을 작성할 때 주로 사용되는 원칙

REST 제약조건

  1. 클라이언트 서버 (Client-Server)
  • API를 통해 정보를 교환하는 주체는 클라이언트와 서버 구조를 가져야 한다.
  • 클라이언트와 서버의 분리를 통해 서로 의존하지 않는 구조를 가져야 한다. (관심사의 분리)
  • HTTP 는 리퀘스트(클라이언트)와 리스폰스(서버)의 구조로서 해당 조건을 만족한다.
  1. 무상태성 (Stateless)
  • 클라이언트는 상태를 저장하지 않는다.
  • 클라이언트의 각 리퀘스트는 서버가 리퀘스트를 이해하는 데에 필요한 모든 정보를 포함해야 한다.
  1. 캐시 (Cache)
  • 데이터 복사본을 임시 저장 위치에 저장하여 보다 빠르게 액세스할 수 있도록 하는 프로세스인 캐싱을 통해 네트워크 효율성을 높인다.
  • 리퀘스트에 대한 리스폰스에 캐시 가능 및 불가능 여부가 들어 있어야 한다.
  • HTTP는 헤더의 Cache-Control를 통한 캐시 가능 여부 명시함으로서 해당 조건을 만족한다.
  1. 일관화된 인터페이스 (Uniform Interface) ✨
  • 전체 시스템을 잘 파악할 수 있도록 일관된 인터페이스를 제공해야 한다.
    이를 통해 구현체가 서비스와 별도로 독립적으로 진화할 가능성을 얻게 된다. (낮은 결합도)
    예를 들어 클라이언트가 업데이트되어도 서버를 함께 업데이트할 필요가 없어진다. (반대의 경우에도 해당)
  • 하위제약조건들
    (1) identification of resources (자원에 대한 식별)
    (2) manipulation of resources through representations (표현을 통한 자원에 대한 조작)
    (3) self-descriptive messages (자기 서술적 메시지)
    (4) hypermedia as the engine of application state, HATEOAS (하이퍼미디어를 사용한 애플리케이션 상태 표현)
  1. 계층화된 시스템 (Layered System)
  • 클라이언트는 서버에 직접 연결되었는지, 중간 서버를 통해 연결되었는지 알 수 없어야 한다.
  • HTTP는 리퀘스트와 리스폰스를 보내는 주체는 중간 계층을 신경 쓰지 않아도 되는 구조로서 해당 조건을 만족한다.
  1. 주문형 코드 (Code on Demand)
  • 서버에서 보낸 코드를 클라이언트에서 실행할 수 있어야 한다. (e.g. JavaScript)
  • 선택적 제약 조건이며 지키지 않아도 REST에는 문제가 없다.
  • HTTP에서는 서버의 코드를 담을 수 있는 Body로서 해당 조건을 만족한다.

URL

  1. 목적
  • 자원의 식별: 접근하고자 하는 자원을 명시하고 그 자원을 식별할 수 있어야 한다
  1. URL 작성 규칙
  • 명사형 사용 (동사형 X)
    URI는 동작(동사)을 가리키는 대신에, 자원(명사)을 가리켜야 합니다. 자원에는 속성이 있듯이, 명사는 동사가 가지지 않는 속성을 가지기 때문입니다.
Bad ❌ 
/get-member-list
/create-new-member

Good ✅
/members
/articles
  • 반환(응답)하는 자원의 종류에 따라 단수형/복수형 사용하기
    여러 개의 자원을 반환한다면 복수형, 단 1개의 자원을 반환한다면 단수형을 사용합니다.
/members         # 멤버 목록 (복수형)
/members/1       # 멤버 목록 중 (복수형), 1번 멤버 (단수형)
/key             # 1개의 키 (단수형)
  • 계층 표현을 위하여, 슬래시(/) 사용
    자원들 간의 계층 표현을 위해서 슬래시(/)를 사용
/articles
/articles/1
/articles/1/comments
/articles/1/comments/2
  • 마지막 슬래시(/) 붙이지 않기
    마지막에 붙는 슬래시를 트레일링 슬래시 (Trailing Slash)라고 하며, REST에서 어떠한 의미도 가지지 않기 때문에 혼동을 줄 수 있어서 붙이지 않음

  • 모두 소문자로 작성 & 띄어쓰기는 대시(-) 사용
    언더스코어(_) 또는 카멜 케이스(CamelCase)는 가독성을 낮출 수 있기에 사용하지 않음
    띄어쓰기를 위한 대시(-)와 함께 소문자를 사용

  • 파일 확장자 포함하지 않음

  • 목록에 필터가 필요할 경우, 쿼리 문자열을 사용
    /articles/1/comments?sort=latest

  • URI에 동사 사용 X

Bad ❌
/members/1/delete
/members/2/update

Good ✅
DELETE /members/1
PUT /members/2

HTTP 메소드의 종류

  1. GET: 자원 가져오기
  • 가져오는 행동이므로 자원은 변화하지 않아야 하며, 여러번 리퀘스트를 보내도 같은 리스폰스를 돌려줌. 자원의 내용이 변하지 않으므로 안전한 메소드라고도 부름.
  • 예: GET /membersmembers라는 정보를 JSON 형태로 받아옴
  1. POST: 자원 조작하기
  • 자원에 대해 무언가를 수행하기에 자원의 변화가 있음
  • 자원 목록에 데이터를 전송하여 새로운 자원의 생성을 요청함
  • 예: 멤버 목록에 자원 (JSON 형태) 추가하기
  1. PUT / PATCH: 자원 수정하기

  2. DELETE: 자원 삭제하기
    정상적으로 삭제되는 경우 보여줄 내용이 없어지기에 상태코드만 발생

✨ 조작의 표현: HTTP Method
✨ 자원의 표현: JSON

PUT vs PATCH

  • PUTPATCH 모두, 의미적으로 자원을 수정할 때 사용할 수 있는 HTTP 메소드이지만, PUT은 자원의 교체(replace), PATCH는 부분 수정(partial update)을 의미한다는 차이가 있음

(1) PUT

  • 수정할 속성
{
    "username": "harry",
    "age": 30
}
  • 리퀘스트

PUT /members/1

{
    "username": "mike"
}
  • 리스폰스
200 OK

{
    "username": "mike",
    "age": null
}
  • id가 1인 멤버의 자원을 리퀘스트에 포함된 자원으로 교체(replace)하였기 때문에 리퀘스트로 전달되지 않은 자원의 속성은 비어 있는 상태로 수정되며, 자원의 표현 시 null로 표시됨

(2) PATCH

  • 리퀘스트
PATCH /members/1

{
    "username": "mike"
}
  • 리스폰스
200 OK

{
    "username": "mike",
    "age": 30
}
  • id가 1인 멤버의 자원을 리퀘스트에 포함된 자원으로 부분 수정(partial update)하였기 때문에, 리퀘스트로 전달된 자원의 속성만 반영됨

상태코드

미디어타입과 링크헤더

메시지에 담긴 자원의 표현을 해석하는 설명을 작성 방식

  1. Content-Type: Media Type
    IANA 미디어타입 사용
  2. Link profile
    API 설명서 링크를 함께 기재하기

HATEOAS

  • HATEOAS (Hypermedia As The Engine Of Application State): 하이퍼미디어를 사용한 애플리케이션 상태 표현 및 변경

  • 현재 페이지에서 어떤 페이지로 이동이 가능한지, 어떤 동작을 수행할 수 있는지 등의 변경 가능한 부분들을 알 수 있어야 한다는 의미!

  • 예: 멤버를 삭제하는 동작 명시 → HATEOAS 만족

HATEOAS를 만족하는 방법:

  1. 리스폰스 바디에 상태 변경을 위한 링크 포함
  • JSON, XML 등의 구조에 외부 자원에 대한 링크를 추가하기 위한 특별한 데이터타입인 HAL(Hypertext Application Language)을 사용!
  • HAL = 리소스 + 링크
  1. 링크 헤더
  • <a> 태그의 구조와 매우 유사

✨ 자주 하는 실수

캐싱

  • 캐시: 중복 request 시 바로 반환하기 위해 재사용을 위해 자원을 보관하는 곳
  • 캐싱: 캐시에 저장된 데이터로 불필요한 과정을 줄여 시간과 비용을 절약하는 행위
  • 헤더: 캐싱할 자원을 판단하고 클라이언트와 서버의 자원정보를 전달하는 역할 ← Cache-Control, Last-Modified, ETag 등의 태그들을 적절히 사용
  • 예: profile 이미지를 받아오는 과정
  • 만약 캐시가 없다면? 시간, 메모리 낭비 발생!
  • Cache-Control 헤더를 사용해서 캐싱한다면? ← max-age : 몇초동안 유효한 캐시인지 명시
  • 이미지가 변경했는지 검증하기 위해 마지막으로 변경된 시점을 저장하는 Last-Modified 헤더를 추가한다면?

    ✨ Last-Modified의 한계: 날짜와 시간을 기반으로 하기에, 1초보다 작은 단위의 캐시관리는 불가능하고, 실제 자원의 변경 없이 수정된 시간만 변경된 경우에도 다른 데이터로 인식하게 됨
  • ETag (Entity Tag)를 활용한다면? ← 문자열 기반
    처음 받은 리스폰스에 ETag 헤더가 있다면 무조건 캐시에 보관하고, max-age 초과 후 다시 리퀘스트를 보내기 전에 If-None-Match 헤더를 사용해서 ETag값과 동일하다면 캐시에 있는 데이터를 그대로 사용함

API 버전 명시하기

  • web API의 추가나 삭제는 크게 문제되지 않으나 변경의 경우 리스폰스 구조변경, 새로운 엔드포인트 생성 등의 방식으로 대처해야 함

  • 버저닝: 수정 시 기존의 이름과 완전히 바뀌게 되면 혼동을 줄 수 있으므로 v1, v2 등과 같이 버전을 명시해주는 것이 권장됨
    예: GET / v2/members

  • 다양한 버전의 형태:

# 1. 
/v2/members

# 2. 
/members?version=2

# 3. 
Accept-Version: v2
/members

페이지네이션

  • 페이지네이션(pagination): 한꺼번에 너무 많은 자원을 받으면 서버에 부담이 갈 수도 있으므로 대부분의 API들이 자원의 양이 많은 경우 적절한 양만큼씩 묶어서 번호를 붙여 n번째 페이지의 개념으로 정리한 후 각 페이지별로 요청하는 방식을 체택함

  • 리스폰스를 받을 때 자원에 대한 표현 + 페이지에 대한 정보를 함께 받아야 함

  • API 내용:

  • 웹페이지 UI 반영사항:

  1. 웹페이지 하단에 페이지별 번호가 붙어있는 경우
  2. 번호 없이 한 페이지의 내용을 전부 스크롤하면 자동으로 하단에서 다음 페이지의 내용이 노출되는 경우

웹 API

0개의 댓글