Netwrok 웹 API 디자인 REST API

1. 웹 API 디자인

대부분 최신 웹 어플리케이션은 클라이언트가 애플래케이션과 상호작용하는 데 사용할 수 있는 API를 표시합니다.

웹 API의 특성 2가지

  1. 플랫폼 독립성
    모든 클라이언트는 내부에서 API구현하는 방법에 관계없이 API를 호출할 수 있어야합니다. 그러기위해서는 표준 프로토콜을 사용해야하고,클라이언트가 및 웹서비스가 교환할 데이터 형식에 동의할 수 있는 매커니즘을 가지고 있어야합니다.

  2. 서비스진화
    Web API는 클라이언트 애플리케이션과 독립적으로 기능을 진화시키고 추가 할 수 있어야합니다. API가 진화해도 기존클라이언트는 수정없이 작동되야하고 해당 기능을 완전히 이용할 수 있도록 검색 가능해야합니다.

2. REST 소개

2000년에 Roy Fielding은 웹 서비스를 디자인하는 아키텍처 접근 방식으로 REST(Representational State Transfer)를 제안했습니다. REST는 하이퍼미디어 기반 분산 시스템을 구축하기 위한 아키텍처 스타일입니다. REST는 어떤 기본 프로토콜과도 독립적이며 HTTP에 연결될 필요가 없습니다. 그러나 대부분의 일반적인 REST 구현에서 애플리케이션 프로토콜로 HTTP를 사용하고, 이 지침에서는 HTTP를 위한 REST API 디자인에 중점을 둡니다.

REST가 HTTP보다 우수한 장점

  1. 개방형 표준
    API또는 클라이언트 애플리케이션의 구현이 특정 구현에 바인딩되지 않습니다.

Restful API 디자인 원칙

  1. 리소스를 중심으로 디자인되며 클라이언트에서 액세스 할 수 있는 모든 종류의 개체, 데이터 또는 서비스가 리소스에 포함됩니다.

  2. 리소스마다 해당 리소스를 고유하게 식별하는 URI식별자 존재

1
2
고객 주문 URI
https://adventure-works.com/orders/1
  1. 클라이언트가 리소스의 표현을 교환하여 서비스와 상호작용합니다.
1
{"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
  1. REST API는 균일한 인터페이스를 사용하므로 클라이언트와 서비스 구현을 분리하는데 도움이 됩니다.

HTTP기반으로 하는 REST API경우 리소스에 표준 HTTP 동사 수행 작업을 사용하는것이 균일한 인터페이스에 포함됩니다.
Ex) GET,POST,PUT,PATCH,DELETE

  1. REST API는 상태 비저장 요청 모델을 사용합니다.

독립적이며 임의순서로 발생할 수 있으므로 요청 사이에 일시적인 상태정보를 유지할 수 없습니다. 그리고 클라이언트 서버 사이에서 연결성을 유지할 필요가 없습니다. 즉, Connectionsless, Stateful, Stateless의 특성을 가지고 있습니다.

  1. REST API는 표현에 포함된 하이퍼미디어 링크에 따라 구동됩니다.
1
2
3
4
5
6
7
8
9
10
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links": [
{"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
{"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
]
}

2008년에 Leonard Richardson은 Web API에 대한 다음과 같은 모델 제안

  • 수준 0: 한 URI를 정의하고 모든 작업은 이 URI에 대한 POST 요청을 통해 진행됩니다.
  • 수준 1: 개별 리소스에 대한 별도의 URI를 만듭니다.
  • 수준 2: HTTP 메서드를 사용하여 리소스에 대한 작업을 정의합니다.
  • 수준 3: 하이퍼미디어(HATEOAS) 를 사용합니다.

3. 리소스를 중심으로 API 구성

웹 API가 표시하는 비즈니스 Entity 에 집중해야합니다.

전자상거래 시스템에서는 기본 엔티티가 고객과 주문인데 주문 정보가 포함된 HTTP POST요청을 전송하여 주문만들기를 진행할 수 있습니다. 이때 가장 중요한점은 리소스 URI는 동사(리소스에 대한작업)이 아닌 명사(리소스)기반으로 설계되어야합니다.

1
2
https://adventure-works.com/orders // Good
https://adventure-works.com/create-order // Avoid

리소스는 단일 물리적 항목을 기반으로 구현할 필요가 없습니다. 내부적으로 관계형 DB로 구현할 수 있지만 클라이언트에 대해서는 단일 엔티티로 표시됩니다. 내부구조를 반영하는 API를 설계하면 안됩니다. 즉, 클라이언트 내부구현에 노출되면 안됩니다.

엔티티는 종종 컬렉션(주문, 고객)으로 그룹화 되는데 컬렉션은 고유한 URI가 있어야합니다.

주문 URI 주문 컬렉션

1
2
컬렉션 URI에 HTTP GET 요청을 보내면 컬렉션에 있는 항목 목록을 검색합니다.
https://adventure-works.com/orders

URI에 일관적인 명명 규칙하는데 컬렉션을 참조하는 URI에 대해 복수 명사를 사용할 수 있습니다. 컬렉션 및 항목에 대한 URI를 계층 구조로 구성해야합니다.

예를 들어 /customers는 고객 컬렉션의 경로이고, /customers/5는 ID가 5인 고객의 경로입니다.

이 접근 방식을 사용하면 웹 API를 직관적으로 유지할 수 있습니다. 또한 많은 Web API 프레임워크는 매개 변수가 있는 URI 경로를 기반으로 라우팅 경로 /customers/{id}에 대한 경로를 정의할 수 있습니다.

서로 다른 리소스 형식과 이러한 연결을 표시하는 방법 사이의 관계도 고려해야 합니다.
예를 들어 /customers/5/orders는 고객 5에 대한 모든 주문을 나타낼 수 있습니다. 반대 방향으로 이동하여 /orders/99/customer 같은 URI를 사용하여 주문에서 고객으로의 연결을 표시할 수도 있습니다.

단, 모델을 광범위하게 확대시 구현이 어려울 수 있습니다.

구현이 어려운 경우

1
/customers/1/orders/99/products

클라이언트가 여러 관계 수준을 탐색할 수 있는 URI를 제공하고 싶을 수 있습니다. 그러나 이 수준의 복잡성은 유지하기 어려울 수 있으며 나중에 리소스 사이의 관계가 변하면 유연성이 떨어집니다.
위와같이 설계시 모든 웹 요청이 웹 서버의 부하를 높인다는 점입니다. 요청이 많을수록 부하가 커집니다. 따라서 다수의 작은 리소스를 표시하는 복잡한 Web API를 피해야합니다.

그러면 어떤식으로 설계해야할까요?

이전 쿼리를 /customers/1/orders URI로 바꿔서 고객 1의 모든 주문을 찾은 후 /orders/99/products로 바꿔서 이 주문의 제품을 찾을 수 있습니다.

그리고 Web API와 기본 데이터 원본 사이에 종속성이 발생하지 않도록 해야 합니다.

예를 들어 데이터가 관계형 데이터베이스에 저장되는 경우 Web API는 각 테이블을 리소스 컬렉션으로 표시할 필요가 없습니다.

마지막으로, 웹 API에 의해 구현된 일부 작업을 특정 리소스에 매핑하지 못할 수 있습니다. HTTP GET 요청을 통해 기능을 호출하고 결과를 HTTP 응답 메시지로 반환하는 리소스가 아닐 수 있습니다.

예시1

더하기 및 빼기 같은 단순한 계산기 작업을 구현하는 Web API 리소스로 표시하고 쿼리 문자열을 사용하여 필요한 매개 변수를 지정하는 URI설계 할 수 있습니다.

예시2

URI /add? operand1 = 99&99&operand2 = 1 에 대 한 GET 요청은 100 값을 포함 하는 본문이 포함 된 응답 메시지를 반환 합니다. 그러나 이러한 형식의 URI는 제한적으로만 사용해야 합니다.

4. HTTP 메서드를 기준으로 작업 정의

HTTP 메서드 정의

  • GET 은 지정된 URI에서 리소스의 표현을 검색합니다. 응답 메시지의 본문은 요청된 리소스의 세부 정보를 포함하고 있습니다.
  • POST 는 지정된 URI에 새 리소스를 만듭니다. 요청 메시지의 본문은 새 리소스의 세부 정보를 제공합니다. 참고로 POST를 사용하여 실제로 리소스를 만들지 않는 작업을 트리거할 수도 있습니다.
  • PUT 은 지정된 URI에 리소스를 만들거나 대체합니다. 요청 메시지의 본문은 만들 또는 업데이트할 리소스를 지정합니다.
  • PATCH 는 리소스의 부분 업데이트를 수행합니다. 요청 본문은 리소스에 적용할 변경 내용을 지정합니다.
  • DELETE 는 지정된 URI의 리소스를 제거합니다.
리소스 POST GET PUT DELETE
/customers 새 고객 만들기 모든 고객 검색 고객 대량 업데이트 모든 고객 제거
/customers/1 오류 고객 1에 대한 세부 정보 검색 고객 1이 있는 경우 고객 1의 세부 정보 업데이트
/customers/1/orders 고객 1에 대한 새 주문 만들기 고객 1에 대한 모든 주문 검색 고객 1의 주문 대량 업데이트 고객 1의 모든 주문 제거

POST, PUT 및 PATCH 차이점

  • POST 요청은 리소스를 만듭니다. 서버는 새 리소스에 대한 URI를 할당하고 클라이언트에 해당 URI를 반환합니다. REST 모델에서는 컬렉션에 POST 요청을 자주 적용합니다. 즉, 프로세스 처리에 사용됩니다.

  • PUT 요청은 리소스를 만들거나 또는 기존 리소스를 업데이트합니다. 클라이언트는 리소스의 URI를 지정합니다. 요청 본문에는 리소스의 완전한 표현이 포함됩니다. 이 URI를 사용하는 리소스가 이미 있으면 리소스가 대체됩니다. 아직 없고 서버에서 리소스 만들기를 지원하는 경우 새 리소스가 생성됩니다.

PUT사용시 주의할점: PUT 요청은 idempotent(멱등성)이여야 합니다.

클라이언트가 동일한 PUT 요청을 여러 번 제출하는 경우 그 결과가 항상 같아야 합니다(같은 값을 사용하여 같은 리소스가 수정)

  • PATCH 요청은 기존 리소스에 부분 업데이트를 수행합니다. 클라이언트는 리소스의 URI를 지정합니다. 요청 본문은 리소스에 적용할 변경 내용을 지정합니다. 즉, 리소스 부분변경을 업데이트합니다.

멱등성의 개념

멱등(Idempotent Methods) 여러번 호출해도 결과는 같아야합니다. PUT을 호출하여 서버응답 호출을 통하여 재시도 할지 안할지를 확인할 수 있습니다. 즉, 자동 복구 매커니즘에서 많이 활용됩니다.

5. 미디어 유형

HTTP 프로토콜에서 형식은 MIME 유형이라고도 하는 미디어 유형을 사용하여 지정됩니다. 보통 Web API는 JSON(미디어 유형 = 애플리케이션/json) 및 XML(미디어 유형 = 애플리케이션/xml)을 지원합니다. 그리고 요청과 응답의 경우 Content-Type 헤더는 표현 형식을 지정합니다.

1
2
3
4
5
6
7
JSON 형식

POST https://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}

만약에, 서버에서 미디어 유형을 지원하지 않으면 HTTP 상태 코드 415(지원되지 않는 미디어 유형)를 반환해야 합니다.

클라이언트 요청

클라이언트 요청에는 클라이언트가 응답 메시지에서 서버로부터 받는 미디어 유형 목록을 포함하는 Accept 헤더가 포함될 수 있습니다.

1
2
GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json

6. GET 메서드

성공적인 GET 메서드는 일반적으로 HTTP 상태 코드 200(정상)를 반환합니다. 리소스를 찾을 수 없는 경우 404(Not Found)반환합니다.

7. POST 메서드

POST 메서드는 새 리소스를 만드는 경우 HTTP 상태 코드 201(만들어짐)을 반환합니다. 새 리소스의 URI는 응답의 Location 헤더에 포함됩니다. 응답 본문은 리소스의 표현을 포함합니다.

만약에 새 리소스를 만들지 않는 경우 메서드는 HTTP 상태 코드 200을 반환하고 작업의 결과를 응답 본문에 포함할 수 있습니다. 또는 반환할 결과가 없으면 메서드가 응답 본문 없이 HTTP 상태 코드 204(컨텐츠 X) 를 반환할 수 있습니다.

8. PUT메서드

PUT 메서드는 POST 메서드와 마찬가지로 새 리소스를 만드는 경우 HTTP 상태 코드 201(만들어짐)을 반환합니다. 이 메서드는 기존 리소스를 업데이트할 경우 200(정상) 또는 204(내용 없음)를 반환합니다. 상황에 따라 기존 리소스를 업데이트할 수 없는 경우도 있습니다.

9. PATCH 메서드

클라이언트는 PATCH 요청을 사용하여 업데이트를 패치 문서 의 형태로 기존 리소스에 보냅니다. 서버는 패치 문서를 처리하여 업데이트를 수행합니다. 패치 문서는 리소스 전체가 아니라 적용할 변경 내용만 설명합니다. 즉, 부분변경만 진행합니다.

두 가지 주요 JSON 기반 패치 형식으로 JSON 패치 및 JSON 병합 패치

JSON 병합 패치는 비교적 간단합니다. 패치 문서는 원래 JSON 리소스와 동일한 구조를 갖지만 변경 또는 추가할 필드의 하위 집합만 포함하고 있습니다. 또한 패치 문서에서 필드 값에 대해 null을 지정하여 필드를 삭제할 수 있습니다.

JSON 기반 패치

1
2
3
4
5
6
{
"name":"gizmo",
"category":"widgets",
"color":"blue",
"price":10
}

JSON 병합패치

1
2
3
4
5
{
"price":12,
"color":null,
"size":"small"
}

결론적으로 원래 리소스가 명시적 null 값을 포함할 수 있으면 패치 문서에서 null이 갖는 특별한 의미 때문에 병합 패치가 적합하지 않습니다. 또한 패치 문서는 서버에서 업데이트를 적용할 순서를 지정하지 않습니다. JSON 패치의 미디어 유형은 application/json-patch+json입니다.

HTTP 상태 코드와 함께 PATCH 요청을 처리할 때 발생할 수 있는 몇 가지 일반적인 오류 조건

오류조건 HTTP 상태 코드
지원되지 않는 패치 문서 형식입니다. 415(지원되지 않는 미디어 형식)
패치 문서의 형식이 잘못되었습니다. 400(잘못된 요청)
패치 문서가 유효하지만 현재 상태에서는 변경 내용을 리소스에 적용할 수 없습니다. 409(충돌)

10. DELETE 메서드

삭제 작업이 성공하면 웹 서버는 프로세스가 성공적으로 처리되었지만 응답 본문에 추가 정보가 포함되지 않았음을 나타내는 HTTP 204 상태 코드로 응답해야 합니다. 리소스가 없는 경우 웹 서버는 HTTP 404(찾을 수 없음)를 반환할 수 있습니다.

11. 비동기 작업

경우에 따라 POST, PUT, PATCH 또는 DELETE 작업을 완료 하는 데 시간이 오래 걸리는 처리가 필요할 수 있습니다. 이 경우 요청 처리가 수락되었지만 아직 완료되지 않았음을 나타내는 HTTP 상태 코드 202(수락됨)를 반환합니다.

즉, 클라이언트가 상태 엔드포인트를 폴링하여 상태를 모니터링할 수 있도록 비동기 요청의 상태를 반환하는 End-Point 표시해야합니다. 202 응답의 Location 헤더에 상태 엔드포인트의 URI를 포함합니다.

엔드포인트 표시 예시

1
2
HTTP/1.1 202 Accepted
Location: /api/status/12345

클라이언트가 이 엔드포인트에 GET 요청을 보내는 경우 응답에 요청의 현재 상태가 포함되어야 합니다. 필요에 따라 예상 완료 시간 또는 작업 취소 링크를 포함하는 경우는 어떻게 표현할 수 있을까요?

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Content-Type: application/json

{
"status":"In progress",
"link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

비동기 작업에서 새 리소스를 만드는 경우 작업 완료 후 상태 엔드포인트에서 상태 코드 303(다른 항목 보기)을 반환해야 합니다. 303 응답에 새 리소스의 URI를 제공하는 Location 헤더를 포함합니다.

1
2
HTTP/1.1 303 See Other
Location: /api/orders/12345

12. 데이터 필터링 및 페이지네이션

클라이언트 애플리케이션에서 비용이 특정 값을 초과하는 모든 주문을 찾아야 한다고 가정해 봅시다.

클라이언트 응용 프로그램은 /orders URI에서 모든 주문을 검색한 후 클라이언트 쪽에서 이러한 주문을 필터링할 것입니다. 이 프로세스는 매우 비효율적입니다. Web API를 호스팅하는 서버의 네트워크 대역폭 및 처리 성능이 낭비됩니다.

동적 쿼리 및 페이지네이션

이 방법 대신, /orders?minCost=n 처럼 API가 URI의 쿼리 문자열에서 필터 전달을 허용할 수 있습니다. 그러면 Web API가 쿼리 문자열의 minCost 매개 변수를 구문 분석 및 처리하고 서버 쪽에서 필터링된 결과를 반환합니다.

1
/orders?limit=25&offset=50

다음과 같이 오프셋을 지정할 수 있습니다.

서비스 거부 공격을 방지하기 위해 반환되는 항목 수를 제한하는 방안

필드 이름을 /orders?sort=ProductID 같은 값으로 가져오는 정렬 매개 변수를 제공하여 데이터를 가져올 때 데이터를 정렬하는 비슷한 전략을 처리할 수 있습니다.

But, 쿼리 문자열 매개 변수는 여러 캐시 구현에서 캐시된 데이터의 키로 사용되는 리소스 식별자의 일부를 구성하기 때문에 이 접근 방식은 캐싱에 나쁜 영향을 끼칩니다.

13. 대용량 이진 리소스에 대한 부분 응답 지원

대용량 응답 시간을 개선하려면 이러한 리소스를 청크로 검색할 수 있게 하는 방안을 고안해야합니다.

예시

Web API가 큰 리소스의 GET 요청에 대해 Accept-Ranges 헤더를 지원해야 합니다. 이 헤더는 GET 작업이 부분 요청을 지원한다는 것을 나타냅니다. 클라이언트 애플리케이션은 바이트 범위로 지정된 리소스 하위 집합을 반환하는 GET을 반환할 수 있습니다.

HEAD

HEAD 요청은 리소스에 대해 설명하는 HTTP 헤더만 반환하고 메시지 본문이 비어 있다는 점을 제외하면 GET 요청과 비슷합니다.

  • 요청 메시지
1
HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1
  • 응답메시지
1
2
3
4
5
HTTP/1.1 200 OK

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580

Content-Length 헤더는 총 리소스 크기를 제공하고, Accept-Ranges 헤더는 해당 GET 작업이 일부 결과를 지원합니다. 첫 번째 요청은 범위 헤더를 사용하여 처음 2500 바이트를 가져옵니다.

1
2
GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499

일부범위만 Content-Range에 넣는방법

메시지 본문에 반환된 실제 바이트 수(리소스의 크기가 아닌)를 지정하며, Content-Range 헤더는 해당 바이트가 리소스의 어느 부분인지(4580 바이트 중 바이트 0-2499)를 나타냅니다.

1
2
3
4
5
6
HTTP/1.1 206 Partial Content

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580

Reference

https://docs.microsoft.com/ko-kr/azure/architecture/best-practices/api-design