클라이언트가 서버에 요청할 때 기대하는 행동. 이것이 왜 필요한지는 URI 설계 문제에서 나온다.

명사와 동사 가르기

URI가 할 일은 리소스를 식별하는 것뿐이다. /read-member-by-id처럼 행위까지 담으면 URI가 두 가지 일을 하게 되므로, 회원이라는 리소스만 /members/{id}로 남기고 무엇을 할지는 따로 정한다. 그 자리를 메서드가 맡는다. 리소스는 명사, 행위는 동사다.

쿼리 파라미터와 메시지 바디

클라이언트가 서버로 데이터를 보내는 길은 둘이다. 쿼리 파라미터로 보내면 GET을 쓰고 주로 정렬과 필터, 검색어를 담는다. 메시지 바디로 보내면 POST, PUT, PATCH를 쓰고 회원 가입이나 상품 주문처럼 리소스를 등록하고 바꾸는 일을 담는다.

네 가지 전송 상황

실제 상황은 네 가지로 갈린다. 이미지나 정적 문서를 가져오는 정적 데이터 조회는 쿼리 파라미터조차 필요 없다. 검색이나 게시판 목록의 정렬과 필터처럼 동적 데이터를 조회할 때는 쿼리 파라미터를 쓴다. HTML Form으로 회원 가입이나 주문을 보내는 경우, 그리고 같은 일을 서버 대 서버나 앱 클라이언트, 웹 클라이언트의 Ajax로 보내는 HTTP API 전송이 나머지 둘이다. 앞의 둘은 조회라 GET이고 뒤의 둘은 변경이라 바디를 쓴다.

GET, POST, PUT, PATCH, DELETE

GET은 리소스를 조회한다. 서버에 넘길 데이터는 쿼리로 보낸다. 메시지 바디로도 보낼 수는 있지만 지원하는 곳이 많지 않아 권하지 않는다.

POST는 요청 데이터를 처리한다. 바디에 담아 보내면 서버가 그것으로 무엇을 할지 정한다. 새 리소스를 등록하는 데 가장 많이 쓰지만 그것만은 아니다. 주문이 결제완료에서 배달시작으로, 다시 배달완료로 넘어가는 것처럼 값이 아니라 프로세스의 상태가 바뀌는 경우에도 쓰고, 그때는 새 리소스가 생기지 않기도 한다. 어떤 URI에 POST가 오면 무엇을 할지는 리소스마다 따로 정해야 한다. 정해진 것이 없다.

PUT은 리소스를 완전히 대체한다. 있으면 덮어쓰고 없으면 만든다. 통째로 갈아치우므로 보내지 않은 필드는 사라진다. 클라이언트가 구체적인 리소스 위치를 알고 URI를 지정한다는 점이 POST와 다르다.

PATCH는 리소스를 부분적으로 변경한다. 통째로 갈아치우는 PUT과 달리 보낸 필드만 바뀌므로, 일부 수정에는 이쪽을 쓴다. 다만 PATCH를 지원하지 않는 서버가 아직 있고 그럴 때는 POST로 돌아간다.

DELETE는 리소스를 제거한다.

이름만 알아둘 넷

나머지 넷은 이름만 알아두면 된다. HEAD는 GET과 같지만 바디 없이 상태 줄과 헤더만 반환한다. OPTIONS는 그 리소스에 어떤 메서드를 쓸 수 있는지 알려주고 주로 CORS에서 쓴다. CONNECT는 대상 서버로 가는 터널을 만들고, TRACE는 경로를 따라 루프백 테스트를 한다.

안전과 멱등

메서드안전멱등캐시 가능
GETOOO
POSTXX거의 안 씀
PUTXOX
PATCHXX거의 안 씀
DELETEXOX

안전하다는 것은 호출해도 대상 리소스가 변하지 않는다는 뜻이다. 조회만 하는 GET은 안전하고 나머지는 무언가를 바꾸므로 안전하지 않다.

멱등하다는 것은 몇 번을 호출해도 결과가 같다는 뜻이다. PUT은 같은 이미지를 몇 번 올려도 마지막에 남는 것이 그 이미지 하나이고, DELETE도 이미 지워진 것을 다시 지워봐야 결과가 같다. 반면 POST는 두 번 호출하면 같은 결제가 두 번 일어날 수 있다.

멱등성이 실무에서 중요한 이유는 재요청 때문이다. DELETE를 보냈는데 서버가 타임아웃으로 응답을 주지 못했을 때, 성공했는지 모르는 채로 다시 보내도 되는지가 여기서 갈린다. 같은 개념이 카프카에서도 나온다.

캐시 가능

캐시 가능은 응답 결과를 캐시해 재사용할 수 있느냐다. 스펙상으로는 GET, HEAD, POST, PATCH가 가능하지만 실제로 쓰이는 것은 GET과 HEAD 정도다. GET은 URL만 키로 잡으면 되어 단순한데, POST와 PATCH는 본문까지 키에 넣어야 해서 구현이 만만치 않다.

멱등성과 안전이 보지 않는 것

멱등성은 재요청 사이에 다른 곳에서 리소스가 바뀌는 것까지 책임지지 않는다. 사용자1이 GET으로 나이 20을 읽고, 사이에 사용자2가 PUT으로 30을 넣고, 사용자1이 다시 GET하면 30이 나온다. 이것은 멱등성이 깨진 것이 아니라 애초에 고려 범위 밖이다.

안전 역시 대상 리소스만 본다. 같은 요청을 계속 보내 로그가 쌓이다 장애가 나더라도, 그 리소스가 변하지 않았다면 안전하다고 말한다.

컨트롤 URI

URI를 명사로만 채우는 원칙도 끝까지 지켜지지는 않는다. POST /orders/{orderId}/start-delivery처럼 동사가 들어간 URI가 나올 수 있고 이것을 컨트롤 URI라고 부른다. 리소스로 최대한 설계하되 어쩔 수 없을 때만 꺼내 쓴다.

조회는 GET, 변경은 POST

POST는 사실상 무엇이든 할 수 있지만 조회에는 GET이 유리하다. 서버끼리 GET이 오면 캐싱하자고 약속하는 경우가 많고 POST는 그 약속에 끼기 어렵기 때문이다. 조회는 최대한 GET, 변경과 프로세스 처리는 POST로 가르고, 정말 애매하면 POST를 쓴다.

관련

출처