REST의 기본 원칙 6가지

REST의 기본 원칙 6가지

1. Client-Server

(기존 HTTP만 사용해도 저절로 잘 지켜진다.)

‘사용자 인터페이스에 대한 관심사’와 ‘데이터 스토리지 문제에 대한 관심사’를 분리하여, 인터페이스가 변경되지 않는 한 클라이언트와 서버가 서로 의존하지 않고 독립적으로 발전할 수 있어야 한다. 즉, 이러한 형태를 구조를 통해 확장성을 개선한다.

→ 확장을 쉽게 할 수 있다는 뜻은 ‘분산 시스템’을 효율적으로 사용할 수 있다는 의미이다.

2. Stateless

(기존 HTTP만 사용해도 저절로 잘 지켜진다.)

클라이언트에서 서버로의 각 요청에는 요청을 이해하는 데 필요한 모든 정보가 포함되어야하며, 서버에 저장된 컨텍스트를 이용할 수 없다. 따라서 세션 상태는 전적으로 클라이언트에 유지되어야 한다.

→ ‘Stateless’의 규칙을 지킴으로, 확장을 쉽게할 수 있다.

3. Cacheable

(기존 HTTP만 사용해도 저절로 잘 지켜진다.)

네트워크 효율성을 개선하기 위해 캐시(Cacheable) 제약 조건을 추가했다. 요청에 대한 응답 내에 캐싱이 가능한 지 여부(cacheable or non-cacheable)에 대한 라벨링을 해야 한다. 만약 응답에 cacheable하다고 되어 있으면 클라이언트는 동일한 요청에 대해서 응답 데이터를 재사용할 수 있게 된다.

→ ex) HTTP Header의 cache-control

4. Uniform Interface

API를 사용하는 클라이언트들에게 노출될, API 자원(Resource)에 대한 균일한 API 인터페이스를 결정해야만 한다. REST에서 말하는 균일한 인터페이스를 얻으려면, 구성 요소의 동작을 안내하는 4가지의 제약을 따르라고 한다.

1) 리소스 식별 (Identification of Resources)

한 줄 요약

리소스가 URI로 식별되면 된다. (주로 잘 지켜진다.)

자세한 설명

비디오, 문서, 이미지 등과 같은 이름을 지정할 수 있는 것은 모두 리소스(Resource)이다. URI(Uniform Resource Identifier)라는 고유 식별자를 사용하여 리소스를 식별한다.

http://sookocheff.com

HTTP를 통해 REST API를 빌드하면, URL을 사용하여 API에서 액세스하는 리소스를 식별 할 수 있다. REST 제약 조건을 충족하려면 각 URL이 단일 리소스에 매핑되어야 하며 이 리소스에 대한 모든 액세스는 해당 URL을 통해 수행된다. 즉, 특정 리소스는 오직 하나의 URL만 가져야 한다. 예를 들어, 배송 애플리케이션에 대한 API를 제공하려는 경우 주문을 나타내는 리소스가 있을 수 있다. 번호 12345 가 매겨진 개별 주문의 URL에는 주문 번호가 포함된 경로가 있다.

/orders/12345

이 간단한 예는 API가 나타내는 각 리소스를 고유하게 나타내는 URL을 만드는 방법을 보여준다.

**2) 표현을 통한 자원 조작

(Manipulation Of Resource through Representations)**

한줄 요약

특정 리소스를 나타내는 URL을 바꾸지 않고도, 표현(Representation)을 활용해서 리소스를 조작할 수 있다. (주로 잘 지켜진다.)

참고) URL은 Resource Representation에 포함되지 않는 정보이다.

자세한 설명

  • RESTful 애플리케이션은 동일한 URI에서 동일한 리소스의 두 개 이상의 표현을 지원할 수 있다.
  • HTTP 메서드인 GET, POST, PUT, DELETE 등을 활용해서 리소스를 조작할 수 있다.
  • 자원에 대한 요청을 할 때, 서버는 자원의 표현(Resource Representation)으로 응답한다. 이 표현은 클라이언트가 이해하고 조작 할 수 있는 형식으로 리소스의 현재 상태를 캡처한다. 표현(Representation)을 추상적으로 설명하면 ‘메타 데이터를 가진 바이트 덩어리’라고 할 수 있다. 이 메타 데이터를 표현(Representation)의 미디어 타입이라고 한다.

    일반적인 API 예시로서는 HTML, JSON 및 XML등이 있다. 서버가 리소스의 표현(Representation)을 보내기 때문에, 클라이언트가 클라이언트의 요구에 맞는 특정 표현(Representation)을 요청할 수 있다. 예를 들어, 클라이언트는 JSON Resource Representation 또는 XML Resource Representation을 요청할 수 있다. 서버는 클라이언트가 요청하는 Representation에 대한 Resource를 제공할 수 있도록 구현되어 있다면 정상적으로 제공할 것이다. 이 개념을 컨텐츠 협상(content negotiation)이라고 한다. API에서 콘텐츠 협상(content negotiation)을 사용하여 여러 클라이언트가 동일한 URL에서 리소스의 다른 표현(Representation)에 액세스하도록 허용 할 수 있다.

    클라이언트가 특정 표현(Representation)을 요청할 때는 HTTP Accept 헤더를 활용하면 된다. 밑의 요청(request)은 주문에 대한 표현(Represnetation)을 plain text 형태로 나타낸 것이다.

      GET /orders/12345
      Accept: text/plain
    

    반면 밑의 요청(request)은 주문에 대한 표현(Representation)을 JSON 형태로 나타낸 것이다.

      GET /orders/12345
      Accept: application/json
    

    API에서 콘텐츠 협상을 사용하면 리소스 URL을 변경하지 않고 기존 클라이언트도 중단하지 않고, 새로운 리소스 표현을 제공할 수 있다. 이렇게하면 클라이언트와 서버가 유연하게 분리할 수 있게 된다.

3) 자기 서술적인 메시지 (Self-descriptive messages)

한 줄 요약

요청(request), 응답(response)와 같은 메시지는, 메시지 그 자체만 보고 무슨 의미인지 파악할 수 있을 정도로 정보가 담겨있어야 한다. (대부분 거의 잘 안 지켜진다.)

자세한 설명

RESTful 시스템에서 제공하는 표현(Representation)에는 클라이언트가 리소스를 이해하고 조치를 취하는 데 필요한 모든 데이터가 포함되어야 한다. 추가 정보가 필요하지만 응답에 포함되지 않은 경우에는, 해당 추가 정보를 가지고 있는 링크가 응답에 제공되어야 한다. 즉, 응답하기 위해 선택한 미디어 유형은 자체 문서화 되어야 하며, 클라이언트가 관심을 가질 수 있는 모든 관련 리소스 또는 작업을 응답에 정보로 포함시켜야 한다.

하지만 JSON에는 웹의 기본 구성 요소인 하이퍼 링크에 대한 기본 제공 지원이 없다. JSON은 API에서 제공하는 리소스를 함께 연결하여 자체 설명 API를 구축해야 한다. 자세한 방법은 밑 링크와 예시 코드를 참고해라.

  • https://www.w3.org/TR/json-ld/#basic-concepts
  • https://sookocheff.com/post/api/on-choosing-a-hypermedia-format/
  • 예시) JSON-API (https://jsonapi.org/)

      {
        "links": {
          "self": "http://example.com/articles",
          "next": "http://example.com/articles?page[offset]=2",
          "last": "http://example.com/articles?page[offset]=10"
        },
        "data": [{
          "type": "articles",
          "id": "1",
          "attributes": {
            "title": "JSON API paints my bikeshed!"
          },
          "relationships": {
            "author": {
              "links": {
                "self": "http://example.com/articles/1/relationships/author",
                "related": "http://example.com/articles/1/author"
              },
            },
            "comments": {
              "links": {
                "self": "http://example.com/articles/1/relationships/comments",
                "related": "http://example.com/articles/1/comments"
              }
            }
          },
          "links": {
            "self": "http://example.com/articles/1"
          }
        }]
      }
    

자세한 예시

  • 예시 1

    self-descriptive (X)

      GET / HTTP/1.1
    

    이 HTTP 요청 메시지는 뭔가 빠져있어서 self-descriptive 하지 못하다.

    ### self-descriptive (O)

      GET / HTTP/1.1
      **Host: www.example.org**
    

    목적지를 추가하면 이제 self-descriptive

  • 예시 2

    ### self-descriptive (X)

      HTTP/1.1 200 OK
      [ { "op": "remove", "path": "/a/b/c" } ]
    

    클라이언트가 이 응답을 받고 해석해야 하는데, 이게 어떤 문법으로 작성됐는 지 모르기 때문에 self-descriptive 하지 못하다.

    ### self-descriptive (X)

      HTTP/1.1 200 OK
      Content-Type: application/json
    
      [ { "op": "remove", "path": "/a/b/c" } ]
    

    어떤 문법으로 작성됐는 지가 추가하더라도, 아직까지 self-descriptive하지 못하다. 왜냐하면 “op”나 “path”가 무엇을 의미하는 지 아직 모른다.

    ### self-descriptive (X)

      HTTP/1.1 200 OK
      Content-Type: application/json-patch+json
    
      [ { "op": "remove", "path": "/a/b/c" } ]
    

    json-patch+json 명세를 들어가서 찾아보면, “op”나 “path”가 무엇을 의미하는 지 알게 된다.

4) HATEOAS (hypermedia as the engine of application state)

한 줄 설명

애플리케이션의 상태가 Hyperlink를 이용해 전이되어야 한다. (대부분 거의 잘 안 지켜진다.)

자세한 설명

  • 어디서 어디로 전이가 가능한지 미리 결정되지 않는다. 어떤 상태로 전이가 완료되고 나서야 그 다음 전이될 수 있는 상태가 결정된다. 쉽게 말해서 링크는 동적으로 변경될 수 있다.
  • 리소스(Resource)를 고유하게 식별하고, 표현(Representation)을 사용하여 리소스 상태를 전달하고, 미디어 타입을 통해 self-descriptive 메시지를 사용함으로써 모든 애플리케이션 상태가 클라이언트에 유지된다. 모든 애플리케이션 상태를 클라이언트에 유지함으로써, 클라이언트와 서버 간의 직접 연결이 필요하지 않게 된다. 이로써 최소한의 리소스로 많은 클라이언트에 서비스를 제공하도록 서버를 확장 할 수 있다. 이러한 특징은 웹을 확장 가능하고 탄력적으로 만들어주었다. 결론적으로 REST 제약 조건으로 API를 설계함으로써, 확장 가능하고 탄력적인 API를 구축 할 수 있다.
  • 특정 리소스가 다른 리소스와 관련이 있을 때에는, 특정 리소스가 다른 리소스의 관련 정보를 가져올 수 있게 URI를 의미하는 링크(HATEOAS)를 포함해야 한다.

장점

  • 요청 URI가 변경되더라도 클라이언트에서 동적으로 생성된 URI를 사용함으로써, 클라이언트가 URI 수정에 따른 코드를 변경하지 않아도 되는 편리함을 제공한다.
  • URI 정보를 통해 들어오는 요청을 예측할 수 있게 된다.
  • Resource가 포함된 URI를 보여주기 때문에, Resource에 대한 신뢰를 얻을 수 있다.
  • 클라이언트가 제공되는 API의 변화에 일일이 대응하지 않아도 되는 편리함을 얻을 수 있다.

자세한 예시

  • 예시 1)

    # 예시 1)

    /assets/img/posts/development-web/2021-06-15-REST의%20기본%20원칙%206가지/Untitled.png

    모든 API들이 하이퍼링크를 통해서 그 다음 상태로 전이가 될 수 있기 때문에, HATEOAS인 것이다.

    # 예시 2)

    /assets/img/posts/development-web/2021-06-15-REST의%20기본%20원칙%206가지/Untitled%201.png

    a 태그를 통해서 하이퍼링크가 나와있고, 하이퍼링크를 통해서 그 다음 상태로 전이가 가능하기 때문에 HATEOAS를 만족한다.

    # 예시 3)

    /assets/img/posts/development-web/2021-06-15-REST의%20기본%20원칙%206가지/Untitled%202.png

    Link라는 헤더가 다른 리소스가 하이퍼링크로 연결되어 있으면서, 다른 리소스를 가리킬 수 있는 역할을 한다. 따라서 HATEOAS를 만족한다.

  • 예시 2)

    기존의 전형적인 HTTP API 응답 형태

    /assets/img/posts/development-web/2021-06-15-REST의%20기본%20원칙%206가지/Untitled%203.png

    HATEOAS가 적용된 REST API 응답 형태

    계좌번호가 "12345"인 계좌의 정보를 조희 하는 경우에 해당 계좌의 상태 ( 잔여 금액 등등..)에 따라 접근 가능한 추가 API들이 links라는 이름으로 제공된다.

    /assets/img/posts/development-web/2021-06-15-REST의%20기본%20원칙%206가지/Untitled%204.png

주의)

위의 Uniform Interface라는 조건은 ‘URI을 어떻게 네이밍 해야 하는 지’, ‘HTTP Methods는 어떤 것을 쓰는 것이 맞는 지’와는 전혀 관계가 없는 조건이다. 자세한 내용은 밑의 노션 페이지를 참고해라.

RESTful API와 HTTP API와 혼동하는 이유

5. Layered System

(기존 HTTP만 사용해도 저절로 잘 지켜진다.)

계층형 시스템 스타일을 사용하면, 각 구성 요소가 상호 작용하는 직계 계층 너머를 볼 수 없도록 구성 요소 동작을 제한하여(=캡슐화하여) 아키텍처를 계층적으로 구성해야 한다.

6. Code on Demend (Optional)

  • 서버에서 코드를 클라이언트로 보내서 실행할 수 있어야 한다.

    → ex) 서버로부터 받은 JavaScript 파일을 브라우저에서 실행시킬 수 있다.

  • REST를 사용하면 애플릿 또는 스크립트 형태로 코드를 다운로드하고 실행하여 클라이언트 기능을 확장 할 수 있습니다. 이는 사전 구현에 필요한 기능의 수를 줄여 클라이언트를 단순화합니다.

References

Roy Fielding 논문

Representational State Transfer (REST)