내가 만든 REST-API는 진짜 REST한가?

nawhew·2021년 11월 17일
0

0. 참고자료

해당 글은 아래의 자료들을 바탕으로 작성하였습니다.자세한 내용은 아래의 글을 확인해주세요.내용, 저작권등의 문제가 있는 경우 댓글이나 메일 부탁드립니다.


1. REST의 시작

1-1. WWW의 탄생

그림 출처 : 1989년 3월 12일, WWW의 탄생

  • CERN의 팀 버너스리에 의해 WWW 탄생
    • 표현형식 : HTML
    • 식별자 : URL
    • 전송 방법 : HTTP

1-2. REST 발표

  • HTTP/1.0 작업에 참여하던 로이필딩의 고민에서 시작

    "How do I improve HTTP without breaking the Web?"
    웹을 깨뜨리지 않고 어떻게 HTTP를 향상 시킬 수 있을까?

  • 첫번째 해답 : HTTP Object Model
  • 그 후 박사논문을 통하여 REST를 정의

    Representational State Transfer: An Architectural Style for Distributed Hypermedia interaction
    REST : 분산 하이퍼미디어 시스템을 위한 아키텍처 스타일

    Architectural Styles and the Design of Network-based Software Architectures

  • 바쁜-개발자들을-위한-rest-논문-요약

2. REST의 제약사항 / 스타일

아래는 로이필딩의 박사학위 논문인 “Architectural Styles and the Design of Network-based Software Architectures”에 나온 REST의 스타일이다.

  1. Client - Server
    • Request / Response 구조
      • 클라이언트는 서버에 요청을 보내고, 응답을 대기한다.
      • 서버가 요청에 대한 결과를 만들어서 응답해줌
    • 클라이언트와 서버의 구성요소가 독립적으로 진화하는데 도움을 준다.
  2. Stateless
    • 서버가 클라이언트의 상태를 보존하지 않는다 (각 요청에 대해 클라이언트의 Context가 서버에 저장 되어선 안됨)
    • 이는 서버가 클라이언트의 상태정보를 저장하지 않고, 들어오는 요청 메시지를 기반으로 작업을 처리하는 것을 의미한다.
  3. Cache
    • 캐시가 가능해야한다. (모든 메세지를 캐싱해야함을 의미하지 않는다.)
      • 클라이언트는 응답을 캐싱할 수 있어야 한다.
      • 모든 서버의 응답은 캐시가 가능한지 아닌지 암묵적 혹은 명시적으로 알 수 있어야 한다.
    • HTTP의 Last-Modified와 E-Tag를 이용하여 구현할 수 있다.
  4. Uniform Interface
    • 구성요소(클라이언트, 서버 등) 사이의 인터페이스는 균일해야 한다. 이 스타일은 다음의 네 제약조건으로 이루어진다
      • identification of resources

      • manipulation of resources through representation

      • self-descriptive messages

      • hypermedia as the engine of application state

        이 스타일(Uniform Interface)에 따르면, REST API는 기본 URI와 Media-Type만으로 이용 할 수 있어야한다.

        위의 제약조건은 아래에서 더 자세히 알아보겠습니다.

  5. Layered System
    • 계층으로 구성이 가능해야하며, 각 레이어에 속한 구성요소는 인접하지 않은 레이어의 구성요소를 볼 수 없어야한다.
    • 클라이언트는 REST API서버만을 호출하며, 순수 비즈니스 로직을 수행하는 API서버 앞단에, 사용자 인증(Authentivation), 암호화(SSL), 로드밸런싱 등을 수행하는 계층을 둘 수 있다.
  6. Code-On-Demand (Optional)
    • 서버가 클라이언트에 프로그램(코드)을 전달하면, 클라이언트는 그 프로그램을 실행 할 수 있어야 한다. (Javascript 등)
    • 이 제약조건은 필수는 아니다.

REST가 대세가 되었고 CMIS와 같은 표준이 나왔지만, 로이필딩은 REST가 아니라 지적하였다.

로이필딩은 위의 REST의 스타일 중 'Code-On-Demand'를 제외한 모든 스타일을 지켜야 REST라 부를수 있다고 하였다. (REST하지 않은 API도 괜찮음. 다만 REST API라 부르면 안됨.)


3. REST의 구성요소

샘플을 통해서 보는 구성요소

  1. Method

    REST에서 행위(CRUD)가 되는 부분으로 HTTP Method를 사용하여 나타낸다

    Microsoft REST API Guidelines(2016)

    • GET, PUT, DELETE, POST, HEAD, PATCH, OPTIONS를 지원해야함.
    • 마이크로소프트에서 발표한 가이드라인에 따르면 7가지의 메소드를 지원해야한다고 하지만, 대부분 아래의 4가지 메소드만 주로 지원한다.
      • 로이필딩은 어떤 조건에서 어떤 방법을 사용해야 하는지에 언급한적없다. 다만 통일된 인터페이스가 되어야 한다는 것을 강조하였다.
    • 아래는 주요 4가지 메소드이다

    • 위에서 나온 멱등성(Idempotent)는 여러번 수행해도 결과가 같은 경우를 의미한다.
      • a라는 값이 있을 때,
        • 로직 상 a++하는 경우는 호출 할 때마다 값이 증가하기 때문에 Idempotent하지 않다.
        • a=4와 같은 명령을 반복적으로 수행한다면 Idempotent하다 (값이 같기 때문)
      • GET, PUT, DELETE의 경우도 내부적인 기능에 따라 멱등하지 않을 수 있다.
        • GET으로 게시물을 조회 할 때, 만약 조회수를 늘리는 기능이 포함되어 있다면 해당 기능은 멱등하지 않다.
      • REST API에서 멱등성이 중요한 이유는 Idempotent 하지 않은 경우엔 트랜잭션 처리에 대한 주의가 필요하기 때문이다. (실패 시 이전 상태로 리소스를 복구해주어야 함)
  2. Resource

    서버에서 명명 할 수 있는 모든 정보는 자원이 될 수 있다. (문서, 이미지, 다른 리소스의 모음 등..)

    Microsoft REST API Guidelines(2016)

    • 리소스는 고유한 ID가 존재하고, 이 자원은 Server에 존재한다.
    • 리소스는 URI를 통하여 구별한다. (identification of resources)
  3. Representation

    Representation은 어떤 리소스의 특정 시점의 상태를 반영하고 있는 정보이며,

    Json, XML, plain-text 등의 여러 형태로 표현할 수 있다.

    “representation”은 무엇인가? 어떤 리소스의 특정 시점의 상태를 반영하고 있는 정보이다. 하나의 representation은 representation data와 representation metadata로 구성된다. 위의 예에서는 “hello”가 representation data이고, “Content-Type: text/plain”과 “Content-Language: en”이 representation metadata이다.

    • 클라이언트에서 리소스의 추가 및 수정 등을 위해 서버로 보내는 리소스의 정보 또한 Representation이며,
        PUT /repos/{owner}/{repo}/topics
        {
          "owner": "octocat",
          "repo": "hello-world",
          "names": [
            "names"
          ]
        }
  • 리소스의 현재 시점의 상태 정보를 반환한 내용 또한 Representation이다.
        HTTP/1.1 200 OK
        {
          "names": [
            "octocat",
            "atom",
            "electron",
            "api"
          ]
        }

단, Representation은 단순히 서버에서 반환해주는 리소스의 정보만이 아닌

  • Content-Type, Content-Language와 같은 Metadata 또한 Representation이다.

Representation metadata는 특정 URI로 자원을 요청하였을 때 서버와 클라이언트 간의 내용 협상(Content Negotiation) 에 사용된다.

  • Content Negotiation을 통해 Representation이 선택되어 반환된다
  • Content-Type을 통해 Representation의 포맷(Json, XML, HTML 등)이 선택되고,
  • Content-Language를 통해 Representation의 언어(한글, 영어 등)이 선택된다.
        Content-Type: text/html; charset=UTF-8
        Content-Language: ko
        
        <html><body>안녕하세요</body></html>
  • 그리고 리소스에 대한 정보가 아닌 성공, 오류시 반환 되는 메세지 또한 Representation이다.
        HTTP/2 422
        Content-Length: 149
        
        {
         "message": "Validation Failed",
         "errors": [
           {
             "resource": "Issue",
             "field": "title",
             "code": "missing_field"
           }
         ]
        }

이는 Unidentified Representation으로 불리는 URI를 모르는 어떤 리소스에 대한 Representation이다.

이론적으로 정확한 정답은 “Content-Location 헤더에 들어있는 uri가 가리키는 리소스”이다. 그러나 보통 Content-Location 헤더는 비어 있을 것이므로 현실적인 정답은 “uri를 모르는 어떤 리소스”이다.

HTTP 메시지의 PayLoad로 전달되는 모든 것은 하나의 Representation이거나 적어도 그의 일부이다.

Re: Is a payload always a representation?
Yes, but not necessarily a representation of the resource identified by the request-target.
- Roy T. Fielding [https://lists.w3.org/Archives/Public/ietf-http-wg/2017JanMar/0523.html]


4. Uniform Interface

4-1. identification of resources

URI(Uniform Resource Identifier)로 리소스가 식별되어야 한다.

http://{serviceRoot}/movie
http://{serviceRoot}/movies
http://{serviceRoot}/movies/inception
http://{serviceRoot}/movies/inception/actors/2
  • 컬렉션(복수) : movies, actors
  • 고유식별자 : inception, 2
  • 리소스는 서브 리소스를 포함 할 수 있다 (위의 4번째 URI)

4-2. manipulation of resources through representations

Representaion 전송을 통해서 리소스를 조작해야한다.

4-3. self-descriptive messages

  • 메시지 스스로 메시지에 대한 설명이 가능해야 한다.
    • 서버가 변해서 메시지가 변해도, 클라이언트는 메시지를 보고 해석이 가능하다.
  • self-descriptive messages 적용 방법
  1. Content-Type Header를 통해 Media Type 추가
    A. 미디어 타입이 없는 경우 새로 정의
    B. 아래의 "id", "title"의 의미를 정의 한 미디어 타입 문서를 작성
    C. IANA에 미디어 타입을 등록 (이때 만든 문서를 미디어 타입의 명세로 등록)
    D. Content-Type에 명시된 Media-Type을 IANA에서 찾아서 메시지의 의미를 해석 가능
    - 단점
    - 매번 미디어 타입을 등록하는 일은 번거로움이 따름
        GET /todos HTTP/1.1
        Host: example.org
        
        HTTP/1.1 200 OK
        Content-Type: application/vnd.todos+json
        
        [
        	{"id": 1, "title": "회사 가기"},
        	{"id": 2, "title": "집에 가기"}
        ]
  1. HTTP Header에 profile 링크 추가
    - 단점
    - 클라이언트가 Link 헤더(RFC5988)와 profile(RFC 6906)을 이해해야함.
    - Content negotiation 불가
    - 이러한 단점으로 아래의 3번 방법을 사용
        GET /todos HTTP/1.1
        Host: example.org
        
        HTTP/1.1 200 OK
        Content-Type: application/json
        Link: <https://exmaple.org/docs/todos>; rel="profile
        
        [
        	{"id": 1, "title": "회사 가기"},
        	{"id": 2, "title": "집에 가기"}
        ]
  1. HAL의 링크 데이터에 profile 링크 추가
        {
          "id" : 31,
          "free" : false,
          "location" : "my home",
        	...
          "_links" : {
            "self" : {
              "href" : "http://localhost:8080/api/events/31"
            },
            "query-events" : {
              "href" : "http://localhost:8080/api/events"
            },
            "profile" : {
              "href" : "/docs/index-kr.html#resources-events-create"
            },
            "update-event" : {
              "href" : "http://localhost:8080/api/events/31"
            }
          }
        }

4-4. Hypermedia As The Engine Of Application State (HATEOAS)

  • 하이퍼미디어(링크)를 통해 애플리케이션의 상태 변화가 가능해야 한다.
  • 링크 정보를 동적으로 바꿀수 있다.
    • 이로인해 API에 Versioning을 할 필요가 없어진다.

      "REST API must be hypertext-driven"
      "REST API를 위한 최고의 버저닝 전략은 API 버저닝을 안하는 것"
      - Roy T. Fielding

  • HATEOAS 적용 방법
    1. HTTP Header 이용
      A. Link Header
      B. Location Header
        POST /todos HTTP/1.1
        Content-Type: application/json
        
        {
        		"title": "점심 약속"
        }
        
        HTTP/1.1 204 No Content
        Location: /todos/1
        Link: </todos/>, rel="collection"
  1. 데이터에 링크를 제공
  • HAL 스펙에 맞추어 데이터에 링크를 제공한다
        {
          "id" : 31,
          "free" : false,
          "location" : "my home",
        	...
          "_links" : {
            "self" : {
              "href" : "http://localhost:8080/api/events/31"
            },
            "query-events" : {
              "href" : "http://localhost:8080/api/events"
            },
            "profile" : {
              "href" : "/docs/index-kr.html#resources-events-create"
            },
            "update-event" : {
              "href" : "http://localhost:8080/api/events/31"
            }
          }
        }

REST의 경우 명확한 표준이 정해지지 않아 지금까지 완전히 REST하지 않은 API를 REST API라고 하면서 개발하고 있었습니다.
제대로된 REST API를 개발하기 위해 REST의 의미와 스타일에 대해 정리해보았습니다.
다음에는 잘 짜여진 REST API의 예시와 Springboot, RestDocs를 이용한 실제 REST API 작성하는 법을 정리해보겠습니다.
감사합니다 :)

profile
개인 기술 블로그 - 개념정리/프로젝트/테스트/오류/짧은지식

0개의 댓글