RESTful-API-간략정리 (1)

개요

RESTful 에 대한 워낙 좋은 글들이 많아 굳이 작성할 필요는 없는 글이지만 개인적인 정리를 위해 작성한다.

Spring MVC 를 활용하여 RESTful 개념으로 적당히 Controller 을 구현하고 구글링을 통해 여러 글들을 접하면서

내가 정리한 RESTful 의 장점은 크게 3가지라고 생각한다.

장점

  1. 읽기 쉽다. 가장 중요하다고 생각. 읽히기 쉬운 인터페이스를 가지면 개발간에 오해 없이 빠른 개발 진행이 가능
  2. HTTP 스펙을 최대한 활용하기 때문에, HTTP 기반이면 뭐든 사용 가능
  3. (사실 두번째 장점의 연장성인데) 여러 플랫폼 / 여러 클라이언트를 대응할 수 있음

RESTful 의 규칙

로이필딩이 정의한 다음의 요건을 만족해야 RESTful 하다고 말할 수 있다.

  • 클라이언트 서버 구조(Client / Server Architectural Style)
  • 무상태(Stateless): 단, 인증/권한을 위한 내용은 실용적인 측면에서 예외가 가능함 캐시 처리 가능(Cacheable)
  • 계층화(Layered System)
  • 균일 인터페이스(Uniform Interface)
  • Code on Demand(optional)

그 중 가장 중요한 규칙이라고 할 수 있는 균일 인터페이스(Uniform Interface)를 지키기 위한 중요한 2가지 요소를 적어보면 다음과 같다.

  • 리소스의 식별
  • 리소스의 조작

그 중 리소스의 식별 의 측면에서 RESTful 특징의 일부를 기술하고자 한다.

리소스의 식별

리소스는 반드시 유일한 식별자를 가져야 한다. 따라서 웹 API의 경우 고유의 URI을 가진다.

또한 URI 만을 통해서 해당 자원의 의미를 충분히 표현할 수 있어야 한다.

다만, 동일한 URI 라도 HTTP method 에 따라 리소스에 관련한 작업 - 조회, 등록, 수정, 삭제를 구분하기 에 URI 자체에는 CRUD 성격의 표현이 들어가선 안 된다.

이를 지키기 위한 간단한 디자인 팁은 다음과 같다.

  • 리소스 표현(URI)에서 동사를 제거하자
  • 리소스 간의 관계를 표현하자
  • snake-case 를 사용하자. 하이픈 활용
  • 확장자를 URI 에 기술하지 말자

1) 리소스 표현(URI) 에서 동사를 제거하자

나쁜 예 / 좋은 예를 제시하여 내용 설명을 대체한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ex) 나쁜 예

/getAllDogs
/locationVerify
/foodNeeded
/createRecurringDogWalk
/giveDirectOrder
/healthCheck
/getRecurringDogWalkSchedule
/getLocation
/getDog
/massDogParty
/getNewDogsSince
/getRedDogs
/getSittingDogs
/dogStateChangesSearch
/replaceSittingDogsWithRunningDogs
/saveDog
1
2
3
4
5
6
7
8
9
ex) 좋은 예

GET /tickets ‐ Retrieves a list of tickets
GET /tickets/12 ‐ Retrieves a specific ticket
POST /tickets ‐ Creates a new ticket
PUT /tickets/12 ‐ Updates ticket #12
PATCH /tickets/12 ‐ Partially updates ticket #12
DELETE /tickets/12 ‐ Deletes ticket #12
GET /tickets/new ‐ 신규 티켓 등록을 위한 form 화면 조회

2) 리소스 간의 관계를 표현하자

보통 리소스는 리소스 독립적이 아니라 리소스 간의 관계를 가지게 된다.

REST 에서는 이를 컬렉션(Collection) 과 도큐먼트(Document) 라는 개념으로 설명하고 있는데 굳이 그런 설명이 없더라도 직관적으로 이해가 가능하다. 다음 예시 URI 를 보자

1
GET /users/{id}/dogs/

URI 를 읽어보면 특정 user 가 소유한 (또는 연관된) dogs 의 리스트를 말하는 것임을 직관적으로 알 수 있다.

내가 URI 에 표현하고자 하는 자원이 복수인지 단수인지를 명확히 구분하여 복수 자원인 경우에는 s 를 꼭 붙여줘야 RESTful 한 URI 설계가 된다.

3) snake-case 를 사용하자. 하이픈 활용

URI 로 자원을 표현하고 싶을 때 공백을 두어 단어간의 의미를 명확히 구분하기를 원할 때가 있다.

예를 들어보면, 현재 시스템에서 유 효한 상태의 user 를 얻고자 할 때 URI 설계를 다음과 같이 할 수도 있다.

1
ex) GET /valid user

그러나 공백이 들어가는 경우 URI 에서는 %20 으로 표현되는데 보기가 안 좋다. 그에 따라 보통 밑줄( _ ) 이나 하이픈 ( - ), 또는 CamelCase 방식으로 작성한다.
여기서는 하이픈 사용을 권장한다.

밑줄을 쓸 경우 링크 표현 시 밑줄이 가려보이는 이슈가 있고, CamelCase 방식은 대소문자가 섞이는 이슈가 있다.

도메인과 달리 URI 레벨에서는 대소문자가 구분된다. 따라서 대소문자를 섞어서 쓸 경우 예상치 못한 이슈가 발생하므로 소문자 통일을 권장한다.

4) 확장자를 URI 에 추가하지 말자

URI 는 리소스의 표현이고 이는 플랫폼이나 클라이언트의 요청 자원 형태에 독립적이어야 한다.

따라서 동일한 자원에 대해 .txt / .csv 등의 확장자는 붙이지 말아야 한다.

1
2
3
4
확장자가 포함된 URI

GET /groups/14/users.txt
GET /groups/14/users.csv

예시에서는 group 아이디 14번이 가지는 users 의 리스트를 표현하는데 각각의 응답 형태가 txt 와 csv 임을 알 수 있다. 하지만 리소스 자체는 group 아이디 14번이 가지는 user 리스트이므로 둘 간의 차이는 없다.

클라이언트가 원하는 리소스 형태에 따라 대응하고 싶다면 HTTP header 에서 Accept 를 활용하여 미디어 타입에 따른 대응이 가능하도록 구현하면 된다.

1
2
3
4
5
6
7
8
9
Accept header 활용

GET /hello HTTP/1.1
Host: remotty.com
Accept: text/plain

GET /hello HTTP/1.1
Host: remotty.com
Accept: text/csv

Accept 에 따라 서버에서 각각 적절한 응답 형식을 리턴하도록 구현하면 URI 고유의 의미를 지키면서 각 클라이언트 요청이 원하는 형 태의 데이터 리턴이 가능하다.
또한 Spring MVC 에서는 Accept 에 따라 메서드 분기가 가능하도록 이미 Controller 의 메서드에 produces / consumes 를 제공하고 있다.

1
2
3
4
5
6
// Spring MVC 의 Controller 에서  produces / consumes 활용
@ResponseBody
@RequestMapping(method = RequestMethod.GET, produces = "application/json; charset=utf8", consumes = "application/json; charset=utf8")
public ResponseEntity retrieveUser(@RequestBody @Valid UserDto.Retrieve retrieveDto, BindingResult bindingResult) {
...
}

stackoverflow springmvc requestmapping article 를 꼭 읽어보시길 바람

리소스의 식별과 관련하여 지금까지 제시한 규칙을 따른다면 RESTful 한 구현에 있어서 URI 설계 쪽에는 큰 이슈가 없을 것이다.

제시된 4가지 팁 이외에 고민해볼만한 한가지 사항을 더 추가하면 다음과 같다.

URI 의 버전 정보를 어떻게 표현할 것인가?

사실 API의 버전에 상관없이 고유한 URI의 측면에서 생각한다면 URI 에 /v1,/v2 등을 기술하여 관리하는 것은 RESTful 하지 않다.
이상적이라면 HTTP 의 header 안에 버전 정보를 기술하여 넣는 것이 맞다.

실제로 github 같은 경우 다음과 같이 자체적인 header 를 사용하여 resource 형태와 versioning 을 표현한다.

1
application/vnd.github+json; version=1.0

header 안에 versioning 을 표현하는 아래와 같은 예시도 있다.

1
2
3
curl https://api.stripe.com/v1/charges \
‐u sk_test_BQokikJOvBiI2HlWgH4o \
‐H "Stripe‐Version: 2017‐06‐05"

stripe api 예시

허나 best practice for api versioning 와 링크도 있듯이, header 안에 versioning 을 넣는 이상적인 방안 뿐만 아니라 URI 에 /v1, /v2 를 넣는 방식에 대한 논의도 많다. 브라우저에서 직접 /v1, /v2 등을 입력하여 접근하기 좋고, 구현하는 쪽이나 사용하는 쪽 둘 다 편하다는 장점을 생각한다면 URI 에 /v1 등을 사용하는 것이 꼭 나쁘다고는 생각하지 않아도 될 듯 하다.

참고 링크