RESTful API 디자인 가이드

김준수·2023년 7월 18일
0

HTTP

목록 보기
2/2
post-thumbnail
post-custom-banner

메인 규칙

REST API 설계 시 가장 중요한 항목은 다음의 2가지로 요약할 수 있습니다.

  • 자원은 URI 표현해야 한다.
  • 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

자원 원형(Resource archetypes)

자원 원형은 REST API 내의 자원들이 어떤 역할을 수행하는지, 어떤 종류의 자원인지를 분류하는 방법입니다. Leonard Richardson의 모델에서는 주로 네 가지 원형을 구분합니다:

  1. 문서(Document): 개별 문서를 나타내며, 특정 주제에 대한 정보를 하나의 엔티티로 표현합니다. 예를 들어, 사용자의 프로필이나 특정 주문에 대한 정보 등이 이에 해당합니다.
    /users/{userId}
  2. 컬렉션(Collection): 동일한 유형의 문서들의 집합을 나타냅니다. 컬렉션 자체도 자원으로 간주되며, 컬렉션 내의 개별 항목에 대한 액세스를 제공합니다.
    /users
  3. 컨트롤러(Controller): HTTP Method만으로 표현할 수 없는 행위를 나타내는 자원입니다. 컨트롤러는 특정 행위를 수행하고, 종종 문서나 컬렉션의 상태를 변경하는 데 사용됩니다. 동사를 사용합니다.
    /users/{userId}/suspend
  4. 스토어(Store): 스토어는 클라이언트가 관리하는 자원 저장소입니다. 스토어는 새 자원(URI)를 생성하지 않습니다. 클라이언트가 자원를 넣고, 꺼내고, 삭제할 시기를 결정할 수 있습니다
    /users/{userId}/subscription

자원 원형에 HTTP Method 적용

자원 원형HTTP 메소드설명예시
DocumentGET자원 조회/users/{userId} - 사용자 정보 조회
PUT자원 전체 업데이트/users/{userId} - 사용자 정보 전체 수정
PATCH자원 부분 업데이트/users/{userId} - 사용자 정보 일부 수정
DELETE자원 삭제/users/{userId} - 사용자 정보 삭제
CollectionGET자원 컬렉션 조회/users - 모든 사용자 조회
POST자원 생성 및 컬렉션에 추가/users - 새 사용자 추가
PUT404 Method Not Allow-
DELETE컬렉션의 모든 자원 삭제/users - 모든 사용자 정보 삭제
StoreGET저장소에서 자원 검색/files - 모든 파일 조회
POST저장소에 자원 추가/users/{userId}/subscription - 구독
PUT404 Method Not Allow-
DELETE저장소에서 자원 삭제/users/{userId}/subscription - 구독취소
ControllerPOST특정 작업 수행/users/{userId}/activate - 계정 활성화
GET상태 정보 또는 작업 결과 조회/system/status - 시스템 상태 조회
PUT404 Method Not Allow-
DELETE404 Method Not Allow-

자원 관계

자원 관계는 자원들 간의 상호 연결성을 나타내며, 어떻게 자원이 서로 관련되어 있는지를 설명합니다. 주로 다음과 같은 관계가 있습니다:

  1. 부모-자식 관계: 한 자원이 다른 자원을 소유합니다. 부모가 없으면 자식도 없습니다.
    /users/{userId}

  2. 연관 관계: 두 자원이 서로 연결되어 있습니다.
    /users/{userId}/order

  3. 명시된 연관 관계: 자원 간의 특별한 관계를 가지고 있거나, 애매할때는 어떤 관계인지도 명시합니다.
    /users/{userId}/recent/order

깊은 관계

깊은 관계를 탐색할 수 있는 URI를 제공하고 싶을 수 있지만 그런 URI은 관리하기가 어렵고 유연성이 떨어집니다.

Avoid
/users/{userId}/friend/10/post - 친구의 게시글 탐색

Good
/users/{userId}/friend - 친구 목록을 가져온 후
/users/{userId}0/post - 친구 게시글 탐색

자원 관계 표현

  1. 링크 포함 (Link Inclusion): 클라이언트에게 자원 간의 관계를 나타내는 링크를 직접 포함합니다. 예를 들어, JSON 형식의 응답에 다른 자원에 대한 링크를 추가할 수 있습니다.

    GET /users/{userId}
    
    {
    	"id": 1,
    	"name": "Hong Gil Dong",
    	"post": "http://api.example.com/users/{userId}/post" //절대경로
    }
  2. 임베딩 (Embedding): 하위 자원을 현재 자원에 포함하여 응답합니다. 이는 클라이언트가 추가 요청을 하지 않고도 관련 자원의 정보를 즉시 얻을 수 있게 해줍니다.

    GET /users/{userId}
    {
    	"id": 1,
    	"name": "Hong Gil Dong",
    	"post": [
    		{
    			"id": "1",
    			"title": "This is title",
    			"content": "This is content"
    		},
    		{
    			"id": "2",
    			"title": "This is title",
    			"content": "This is content"
    		}
    	]
    }
  3. 하이퍼미디어 컨트롤 (HATEOAS): 클라이언트가 현재 자원에서 다음으로 이동할 수 있는 가능한 작업을 제시합니다. 이를 통해 클라이언트는 상태 전이(state transition)를 수행하고 자원 간의 관계를 탐색할 수 있습니다.

    GET /users/{userId}
    {
    	"id": 1,
    	"name": "Hong Gil Dong",
    	"links": [
    		{
    			"rel": "self",
    			"href": "http://api.example.com/users/{userId}"
    		},
    		{
    			"rel": "post",
    			"href": "http://api.example.com/users/{userId}/post"
    		}
    	]
    }

URL 규칙

소문자를 사용한다.

Avoid
http://api.example.com/post/1/POST-COMMENT

Good

http://api.example.com/post/1/post-comment

자원는 '/' 로 계층 관계를 구분한다.

http://api.example.com/post/1

URL 마지막에 / 포함하지 않는다.

Avoid
http://api.example.com/post/1/

Good
http://api.example.com/post/1

_(underbar) 대신 -(dash)를 사용한다.

Avoid
http://api.example.com/post/1/post_comment

Good
http://api.example.com/post/1/post-comment

파일 확장자는 URI에 포함시키지 않는다.

Avoid
http://api.example.com/post/picture.jpg

Good
http://api.example.com/post/picture

대신 Accept와 Content-Type 헤더를 사용.

request

 GET /post/picture HTTP/1.1
 Accept: image/jpg

response

 HTTP/1.1 200 OK
 Content-Location: /post/picture?format=jpg
 Content-Type: image/jpeg

참조: 콘텐츠 협상

행위(method)는 URL에 포함하지 않는다.

Avoid

POST http://api.example.com/delete-post/1

Good

DELETE http://api.example.com/post/1

Control Resource는 예외적으로 동사를 허용한다.

HTTP Method로 표현 할 수 없는 기능은 Control Resource로 제작하고 동사로 짓는다.

Avoid

http://api.test.com/posts/duplicating

Good

http://api.test.com/posts/duplicate

HTTP Header

Content Type

data를 응답시 Content-Type의 MIME을 application/json만 제공한다. 다른 형식(xml)은 필요한게 아니라면 굳이 지원할 필요가 없습니다.

Location

POST로 자원 생성 후 생성된 자원를 위치를 표기한다
자원에 접근할때 GET, PUT, DELETE는 이미 자원의 위치를 알고 요청한다. 하지만 POST는 자원를 생성한뒤 새로 생성된 자원의 위치를 요청자가 모르기 때문에 알려 줘야한다.
이때 생성된 자원 접근 위치를 Location header에 표기한다.

request
POST /image

response

 HTTP/1.1 201 OK
 Content-Type: image/png
 Location: /image/photo - 생성 위치

Content-Location

특정 Content-Type의 자원의 위치를 가르키는 헤더이다.

 Content-Type: image/png
 Location: /image/photo
 Content-Location: /image/photo?format=png

Retry-After

비정상적인 방법(DoS, Brute-force attack)으로 API 서버를 이용하려는 경우 429 Too Many Requests 오류 응답과 함께 일정 시간 뒤 요청할 것을 나타낸다.

 HTTP/1.1 429 Too Many Requests
 Retry-After: 3600

Authorization

token 방식의 인증을 사용할때 사용한다.

GET /admin
Authorization: Bearer YOUR_ACCESS_TOKEN

HTTP Status Code

HTTP 응답 상태 코드는 특정 HTTP 요청이 성공적으로 완료되었는지 알려줍니다. 응답은 5개의 그룹으로 나누어집니다.

1xx(정보) : 정보 제공 응답
2xx(성공) : 요청 성공 응답
3xx(리다이렉션) : 리다이렉트 응답
4xx(클라이언트 오류) : 클라이언트 에러 응답
5xx(서버 오류) : 서버 에러 응답

정보 응답 (Informational responses)

  • 100 Continue: 이 코드는 서버가 요청의 초기 부분을 받았고 나머지를 계속 보내도 좋다는 것을 클라이언트에게 알립니다.

성공 응답 (Successful responses)

  • 200 OK: 요청이 성공적으로 처리되었습니다.
  • 201 Created: 새로운 리소스가 성공적으로 생성되었습니다.
  • 204 No Content: 요청은 성공적이었지만 전송할 콘텐츠는 없습니다.

리다이렉션 메시지 (Redirection messages)

  • 301 Moved Permanently: 리소스의 URI가 영구적으로 변경되었습니다.
  • 302 Found: 리소스의 URI가 일시적으로 변경되었습니다.
  • 307/308 Redirect: 요청된 리소스가 다른 URI로 임시 또는 영구적으로 이동했습니다.

클라이언트 에러 응답 (Client error responses)

  • 400 Bad Request: 요청이 잘못되어 서버가 이해할 수 없습니다.
  • 401 Unauthorized: 인증 필요.
  • 403 Forbidden: 권한 필요. 접근 거부.
  • 404 Not Found: 요청한 리소스를 서버에서 찾을 수 없습니다.

서버 에러 응답 (Server error responses)

  • 500 Internal Server Error: 서버 내부에 오류가 발생하여 요청을 수행할 수 없습니다.
  • 503 Service Unavailable: 서버가 일시적으로 요청을 처리할 준비가 되지 않았습니다.

HTTP method 별 코드

HTTP Method200 OK201 Created204 No Content400 Bad Request
GET리소스 성공적 반환--잘못된 요청
POST리소스 생성 성공 후 데이터를 얻고 싶을때리소스 생성 성공-잘못된 요청
PUT리소스 업데이트됨업데이트로 인해 새 리소스 생성됨변경 없이 성공잘못된 요청
DELETE--삭제 후 콘텐츠 없음잘못된 요청

의미에 맞는 HTTP status를 리턴한다

  • Avoid
   HTTP/1.1 200 OK
   {
      "result" : false
      "status" : 400
  }

status는 200으로 성공인데 body 내용엔 실패에 관한 내용을 리턴하고 있다.

모든 응답을 200으로 처리하고 body 내용으로 성공|실패를 판단하는 구조에서 사용된다. 잘못된 설계다.

  • Good
   HTTP/1.1 400 Bad Request
   {
      "msg" : "check your parameter"
  }

세부 에러 사항은 응답 객체에 표시하거나, 해당 에러를 확인할 수 있는 link를 표시한다.

http 상태 코드를 응답 객체에 중복으로 표시할 필요 없다.

  • Bad
HTTP/1.1 404 Not Found
   {

      "code" : 404,
      "error_code": -765
      "more_info" : "https://api.test.com/errors/-765"
  }
  • Good
 	HTTP/1.1 404 Not Found
   {
      "error_code" : -765,
      "more_info" : "https://api.test.com/errors/-765"
  }

파라미터 요구사항 위반

클라이언트 요청이 미리 정의된 파라미터 요구사항을 위반한 경우
파라미터의 위치(path, query, body), 사용자 입력 값, 에러 이유를 알린다

case 1

{
    "message" : "'name'(body) must be Number, input 'name': test123"
}

case 2

{
    "errors": [
        {
            "location": "body",
            "param": "name",
            "value": "test123",
            "msg": "must be Number"
        }
    ]
}

쿼리 매개변수

페이징 (Paging)

한 번에 모든 결과를 응답하지 않고 적당한 크기로 데이터 셋을 나눠서 응답한다.

  • offset : 페이지의 번호
  • limit : 페이지당 자원 개수

예시

GET /users?offset=1&limit=10

정렬(Odering)

정렬 매개 변수에는 정렬이 수행되는 속성의 이름이 쉼표로 구분되어 있어야 한다.

order 또는 sort라는 key를 사용한다.

  • 오름차순: key
  • 내림차순: -key

예시

GET /users?order=-email,name

필터링(Filtering)

필터링은 일부 속성과 예상 값을 지정하여 쿼리된 자원 수를 제한하는 것으로 구성된다.

동시에 여러 속성에 대한 컬렉션을 필터링하고 하나의 필터링된 속성에 여러 값을 허용할 수 있다.

  • AND, OR
  • =, !=
  • > , >=
  • <, >=
  • IN(OR), NOT IN
  • LIKE(include)

예시

http://api.example.com?type=ssd or hdd

필드 선택(Field-Selecting)

결과의 일부분만 선택해서 응답받을 수 있다.

일부 필드만

GET /users?fields=name,level
HTTP/1.1 200 OK
{

"name" : "david",
"level": 10

}

참조

RESTful API 설계 가이드

REST API 디자인 가이드

상태코드 정리

relationships-and-sub-resources.md (github.com)

리소스 중심 디자인

post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 7월 18일

정말 좋은 글 감사합니다!

답글 달기
comment-user-thumbnail
2023년 7월 18일

잘 읽었습니다. 좋은 정보 감사드립니다.

답글 달기