Application Programming Interface (API)

기계와 기계, 소프트웨어와 소프트웨어 사이의 정보 요청과 교환을 위한 창구

소프트웨어가 다른 소프트웨어에게 지정된 형식으로 요청, 명령 받을 수 있는 수단을 Application Programming Interface (API)라 합니다. 꼭 네트워크 상에서만 API가 존재하는 것은 아니고 브라우저는 Web API를 통해 JavaScript로부터 특정 동작을 지시받기도 합니다. 윈도우에는 개발자들이 프로그램을 개발할 때 시스템이나 하드웨어에 대한 세세한 지식 없이도 지정된 명령어로 윈도우가 동작을 수행하도록 소프트웨어를 구성할 수 있는 Windows API를 제공합니다.

일반적인 REST API의 의미

정보의 송수신에 널리 쓰이는 일종의 형식
REST API가 뭔가요? - 얄팍한 코딩사전

프론트엔드 웹에서 서버에 데이터를 요청하거나 배달 앱에서 서버에 주문을 넣는 등의 서비스에서 오늘 날 널리 사용되는 형식이 REST API입니다. 과거의 SOAP이라는 복잡한 형식을 대체하여 만들어졌습니다. REST의 가장 중요한 특성은 각 요청이 어떤 동작이나 정보를 위한 것인지를 그 요청의 모습 자체로 추론 가능하다는 것입니다. 바꾸어 말하면, RESTful하게 만든 API는 요청을 보내는 주소만으로도 대략적으로 어떤 요청인지 파악이 가능하다는 것입니다.

https://(도메인)/classes                // 모든 class 목록 정보 요청
https://(도메인)/classes/2              // index가 2인 class에 대한 정보 요청
https://(도메인)/classes/2/students     // index가 2인 class의 학생들에 대한 정보 요청
https://(도메인)/classes/2/students/15  // index가 2인 class의 학생들 중 index가 15인 학생의 정보 요청

위와 같은 조회 작업 뿐만 아니라 정보를 새로 추가하거나, 수정하거나, 삭제하는 작업도 필요합니다. 이들을 통틀어서 CRUD (Create, Read, Update, Delete)라 부릅니다.

서버에 REST API로 요청을 보낼 때는 HTTP (HyperText Transfer Protocol)이라는 규약에 따라 신호를 전송하는데, 아래와 같은 메서드를 통해 요청을 보낼 수 있습니다.


Image source: MDN Web Docs - mozilla

POST, PUT, PATCH 메서드는 body라는 공간이 마련되어 있어 GET이나 DELETE와 같은 메서드 보다 안전하게 많은 정보들을 감추어서 실어보낼 수 있습니다. 또한, 특정 메서드가 특정 용도로 사용하게끔 제한되어 있지는 않지만, 누구든 각 요청의 의도를 쉽게 파악할 수 있도록 RESTful하게 API를 만들기 위해서는 이들을 목적에 따라 구분해서 사용하는 것이 좋습니다.

  • GET: 데이터를 조회하는데 사용합니다 (Read).
    • 예) GET https://(도메인)/classes/2/students -> 2 반 학생들을 조회하는 요청
  • POST: 새로운 정보를 추가하는데 사용합니다 (Create).
    • 예) POST https://(도메인)/classes/2/students -> 2 반에 새로운 학생 추가 요청
  • PUT/PATCH: 기존 정보를 수정하는데 사용합니다 (Update).
    • 예) PATCH https://(도메인)/classes/2/students/15 -> 2 반 15 번 학생의 정보 수정 요청
    • PUT: 정보를 통째로 갈아끼울 때
    • PATCH: 정보 중 일부를 변경할 때

항상 요청의 내용을 구분 가능하도록 적합한 메서드로 요청하며, 특별한 경우를 제외하고는 URI의 이름을 명사형으로 짓습니다 (메서드 + URI -> 요청의 의미 파악 가능).

REST

분산 하이퍼미디어 시스템 (예: 웹)을 위한 아키텍쳐 스타일
그런 REST API로 괜찮은가 - Naver Deview 17

아키텍쳐 스타일은 제약조건의 집합입니다.

REST를 구성하는 스타일

  • client-server
  • stateless
  • cache
  • uniform interface
  • layered system
  • code-on-demand (optional): 서버에서 코드를 클라이언트로 보내서 실행시킬 수 있어야 한다는 개념 (JavaScript)

위 아키텍쳐 스타일 중 uniform interface를 가장 만족시키지 못하고 있습니다.

Uniform interface의 제약조건

  • identification of resources: Resource가 URI을 통해 식별될 수 있을 것
  • manipulation of resources through representations: Representation 전송을 통해 리소스를 조작할 것
  • self-descriptive messages: 메시지는 스스로를 설명할 것
  • hypermedia as the engine of application state (HATEOAS)

self-descriptive messages

메시지는 스스로를 설명할 수 있어야 한다는 개념입니다. GET / HTTP/1.1와 같은 HTTP 요청 메시지는 무엇인가 빠져있어서 self-descriptive하다고 할 수 없습니다. 하지만 아래와 같이 목적지를 추가하면 self-descriptive 하다고 할 수 있습니다.

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

아래는 self-descriptive한 response의 예입니다.

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

[ { "op": "remove", "path": "/a/b/c"} ]

HATEOAS

애플리케이션의 상태는 hyperlink를 통해 전이되어야 한다는 개념입니다.

아래 예시에서는 a 태그의 하이퍼 링크를 통해 다음 상태로 전이가 가능하기 때문에 HATEOAS를 만족합니다.

HTTP/1.1 200 OK
Content-Type: text/html

<html>
<head></head>
<body><a href="/test">test<a/></body>
</html>

아래의 또 다른 예시는 JSON을 통해 HATEOAS를 만족시키는 방법을 보여주고 있습니다. 링크 헤더를 통해 해당 리소스와 연결되어 있는 다른 리소스를 가리킬 수 있습니다.

HTTP/1.1 200 OK
Content-Type: application/json
Link: </articles/1>; rel="previous",
      </articles/3>; rel="next";
      
{
    "title": "The second article",
    "contents": "blah blah..."
}

왜 Uniform interface가 필요한가요?

독립적 진화를 하기 위해 필요합니다. 독립적 진화란 아래를 의미합니다.

  • 서버와 클라이언트가 각각 독립적으로 진화합니다.
  • 서버의 기능이 변경되어도 클라이언트를 업데이트할 필요가 없습니다.
  • REST를 만들게 된 계기: "How do I improve HTTP without breaking the Web."

REST가 지켜지고 있는가?

웹의 경우에는 아래와 같은 특징들로 비추어 보아 REST가 지켜지고 있다고 볼 수 있습니다.

  • 웹 페이지를 변경했다고 웹 브라우저를 업데이트할 필요는 없다.
  • 웹 브라우저를 업데이트 했다고 웹 페이지를 변경할 필요도 없다.
  • HTTP 명세가 변경되어도 웹은 잘 동작한다.
  • HTML 명세가 변경되어도 웹은 잘 동작한다.

아래와 같이 일부 깨지는 현상은 있을 수 있지만 동작은 합니다.

하지만 우리가 만드는 모바일 앱은 항상 아래와 같은 컴플레인을 받습니다. 서버가 변경된 경우 클라이언트쪽에서 업데이트가 필요한 경우 이런 일이 발생할 수 있습니다.

REST가 웹의 독립적 진화에 도움을 주었나?

  • HTTP에 지속적으로 영향을 줌
  • Host 헤더 추가
  • 길이 제한을 다루는 방법이 명시 (414 URI Too Long 등)
  • URI에서 리소스의 정의가 추상적으로 변경됨: "식별하고자 하는 무언가"
  • 기타 HTTP와 URI에 많은 영향을 줌
  • HTTP/1.1 명세 최신판에서 REST에 대한 언급이 들어감
  • Reminder: Roy T. Fielding이 HTTP와 URI 명세의 저자 중 한 명임

REST는 성공했는가?

  • REST는 웹의 독립적 진화를 위해 만들어졌다
  • 웹은 독립적으로 진화하고 있다

성공!

그런데 REST API는?

  • REST API는 REST 아키텍쳐 스타일을 따라야 한다.
  • 오늘날 스스로 REST API라고 하는 API들의 대부분이 REST 아키텍쳐 스타일을 따르지 않는다.

REST API도 REST 제약조건들을 다 지켜야 하는가?

"An API that provides network-based access to resources via a uniform interface of self-descriptive messages containing hypertext to indicate potential state transitions might be part of an overall system that is a RESTful application
-- Roy T. Fielding

그렇다고 합니다.

왜 API는 REST가 잘 되지 않는가

일반적인 웹과 비교해보겠습니다.

흔한 웹 페이지HTTP API
ProtocolHTTPHTTP
커뮤니케이션사람-기계기계-기계
Media typeHTMLJSON

HTML vs JSON

HTMLJSON
Hyperlink됨 (a 태그 등)정의되어있지 않음
Self-descriptive됨 (HTML 명세)불완전*
  • 문법 해석은 가능하지만 의미를 해석하려면 별도로 문서 (API 문서 등)가 필요하다.

HTML

GET /todos HTTP/1.1
Host: example.org

HTTP/1.1 200 OK
Content-Type: text/html

<html>
<body>
<a href="https://todos/1">회사 가기</a>
<a href="https://todos/2">집에 가기</a>
</body>
</html>

Self Descriptive
1. 응답 메시지의 Content-Type을 보고 media type이 text/html임을 확인한다.
2. HTTP 명세에 media type은 IANA에 등록되어 있다고 하므로 IANA에서 text/html의 설명을 찾는다.
3. IANA에 따르면 text/html의 명세는 http://www.w3.org/TR/html 이므로 링크를 찾아가 명세를 해석한다.
4. 명세에 모든 태그의 해석 방법이 구체적으로 나와 있으므로 이를 해석하여 문서 저자가 사용자에게 의도한 내용을 전달할 수 있다.

HATEOAS
1. a 태그를 이용해 표현된 링크를 통해 다음 상태로 전이될 수 있으므로 HATEOAS를 만족한다.

JSON

GET /todos HTTP/1.1
Host: example.org

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

[
    {"id": 1, "title": "회사 가기"},
    {"id": 2, "title": "집에 가기"},
]


https://bit.ly/32XfeMi
createdDateTime
updatedDateTime
주는 타입, 저장하는 타입

Self-descriptive
1. 응답 메시지의 Content-Type을 보고 media type이 application/json임을 확인한다.
2. HTTP 명세에 media type은 IANA에 등록되어 있다고 하므로 IANA에서 application/json의 설명을 찾는다.
3. IANA에 따르면 application/json의 명세는 draft-ietf-jsonbis-rfc7159bis-04 이므로 링크를 찾아가 명세를 해석한다.
4. 명세에 json 문서를 파싱하는 방법이 명시되어 있으므로 성공적으로 파싱에 성공한다. 그러나 "id"가 무엇을 의미하고, "title"이 무엇을 의미하는지 알 방법은 없다.

HATEOAS
1. 다음 상태로 전이할 링크가 없다.

Self-descriptive와 HATEOAS가 독립적 진화에 어떻게 도움이 되는가?

  • Self-descriptive: 확장 가능한 커뮤니케이션
    • 서버나 클라이언트가 변경되더라도 오고 가는 메시지는 언제나 self-descriptive 하므로 언제나 해석이 가능하다.
  • HATEOAS: 애플리케이션 상태 전이의 late binding
    • 어디서 어디로 전이가 가능한지 미리 결정되지 않는다. 어떤 상태로 전이가 완료되고 나서야 그 다음 전이될 수 있는 상태가 결정된다. 쉽게 말하면 링크는 동적으로 변경될 수 있다.

REST API로 고쳐보자

Self-descriptive - 방법 1. Media type

Content-Type: application/json -> Content-Type: application/vnd.todos+json
1. 미디어 타입을 하나 정의한다.
2. 미디어 타입 문서를 작성한다. 이 문서에 "id"가 무엇이고 "title"이 무엇인지 의미를 정의한다.
3. IANA에 미디어 타입을 등록한다. 이 때 만든 문서를 미디어 타입의 명세로 등록한다.

  • 단점: 매 번 media type을 정의해야 한다.

Self-descriptive - 방법 2. Profile

Link: <https://example.org/docs/todos>; rel="profile"
1. "id"가 무엇이고 "title"이 무엇인지 의미를 정의한 명세를 작성한다.
2. Link 헤더에 profile relation으로 해당 명세를 링크한다.
3. 이제 메시지를 보는 사람은 명세를 찾아갈 수 있으므로 이 문서의 의미를 온전히 해석할 수 있다.

  • 단점:
    • 클라이언트가 Link 헤더 (RFC 5988)와 profile(RFC 6906)을 이해해야 한다.
    • Content negotiation을 할 수 없다.

HATEOAS - 방법 1. data로

[
  {
    "link": "https://example.org/todos/1",
    "title": "회사 가기"
  },
  {
    "link": "https://example.org/todos/2",
    "title": "집에 가기"
  }
]

// or Use URI Template

{
  "links": {
    "todo": "https://example.org/todos/{id}"
  },
  "data": [{
    "id": 1,
    "title": "회사 가기"
  }, {
    "id": 2,
    "title": "집에 가기"
  }]
]

// use other specs
Content-Type: application/vnd.api+json
Link: <https://example.org/docs/todos>; rel="profile"

{
  "data": [{
    "type:: "todo",
    "id": "1",
    "attributes": { "title": "회사 가기" },
    "links": { "self": "http://example.com/todos/1" }
  }, {
    "type:: "todo",
    "id": "2",
    "attributes": { "title": "집에 가기" },
    "links": { "self": "http://example.com/todos/2" }
  }
  1. data에 다양한 방법으로 하이퍼링크를 표현한다.
  • 단점: 링크를 표현하는 방법을 직접 정의해야 한다.
  1. 또는 JSON으로 하이퍼링크를 표현하는 방법을 정의한 명세들을 활용한다.
    • JSON API
    • HAL
    • UBER
    • Siren
    • Collection+json
    • ...
  • 단점: 기존 API를 많이 고쳐야 한다 (침투적).

HATEOAS - 방법 2. HTTP 헤더로

POST /todos HTTP/1.1
Content-Type: application/json

{
  "title": "점심 약속"
}

HTTP/1.1 204 No Content
Location: /todos/1
Link: </todos/>; rel="collection"
  1. Link, Location 등의 헤더로 링크를 표현한다.
  • 정의된 relation만 활용한다면 표현에 한계가 있다.

의문점

Hyperlink는 반드시 URI여야 하는 것은 아닌가?

그렇지 않다.

Media type 등록은 필수인가?

"A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API).
-- Roy. T. Fielding

그렇지 않다. 사용자가 이해할 수만 있으면 된다. 하지만 하면 좋다.

IANA에 Media type을 등록하면 좋은 점

  • 누구나 쉽게 사용할 수 있게 된다
  • 이름 충돌을 피할 수 있다
  • 등록이 별로 어렵지 않다(고 주장함)..

Summary

  • 오늘날 대부분의 "REST API"는 사실 REST를 따르고 있지 않다.
  • REST의 제약조건 중에서 특히 Self-descriptive와 HATEOAS를 잘 만족하지 못한다.
  • REST는 긴 시간에 걸쳐 (수십 년) 진화하는 웹 애플리케이션을 위한 것이다.
  • REST를 따를 것인지는 API를 설계하는 이들이 스스로 판단하여 결정해야 한다.
  • REST를 따르겠다면 Self-descriptive와 HATEOAS를 만족시켜야 한다.
    • Self-descriptive는 custom media type이나 profile link relation 등으로 만족시킬 수 있다.
    • HATEOAS는 HTTP 헤더나 본문에 링크를 담아 만족시킬 수 있다.
  • REST를 따르지 않겠다면, "REST를 만족하지 않는 REST API"를 뭐라고 부를지 결정해야 할 것이다.
    • HTTP API라고 부를 수도 있고
    • 그냥 이대로 REST API라고 부를 수도 있다.

참고자료

  1. REST API가 뭔가요? - 얄팍한 코딩사전
  2. 그런 REST API로 괜찮은가 - Naver D2 DEVIEW 17
profile
합리적인 해법 찾기를 좋아합니다.

0개의 댓글