같은 주소로 요청을 보내도 그 앞에 어떤 메서드가 붙느냐에 따라 결과는 완전히 달라진다. 어떤 요청은 데이터를 조회할 뿐이고, 어떤 요청은 새 데이터를 만들며, 어떤 요청은 기존 데이터를 지운다. 이 차이를 결정하는 것이 메서드다. 메서드는 요청 라인의 첫 토큰이자, 요청의 의도를 한 단어로 선언하는 자리다.


메서드를 정확히 이해하지 못하면 시스템의 동작이 예측 불가능해진다. 조회에 쓰여야 할 메서드로 데이터를 바꾸거나, 여러 번 호출해도 안전해야 할 요청이 매번 다른 결과를 내면, 그 위에 놓인 캐시와 재시도 로직이 전부 어긋난다. 이번 편은 주요 메서드가 각각 무엇을 표현하며 어떤 성질을 약속하는지, 그리고 그 성질이 왜 시스템 설계에 중요한지를 다룬다.

메서드가 표현하는 것

메서드는 자원에 대해 무엇을 하려는지를 나타내는 동사다. 자원이 명사라면 메서드는 그 명사에 가해질 동작을 지정한다. 조회하고, 만들고, 교체하고, 부분을 고치고, 삭제하는 행위가 각각의 메서드로 표현된다. 규약은 이 동사들에 정해진 의미를 부여했고, 클라이언트와 서버는 그 의미를 공유한다.


메서드의 의미가 표준으로 정해져 있다는 사실이 중요하다. 서버가 임의로 조회 메서드를 삭제 동작에 연결하면, 그 서버는 규약을 어긴 것이고 예측 가능성을 잃는다. 표준을 따르면 서버를 처음 보는 클라이언트도 각 메서드가 무엇을 할지 짐작할 수 있다. 메서드는 문서 없이도 통하는 공통의 약속이다.


메서드는 크게 두 부류로 나눌 수 있다. 자원의 상태를 바꾸지 않는 조회성 동작과, 자원을 만들거나 고치거나 지우는 변경성 동작이다. 이 구분은 뒤에서 다룰 안전성이라는 성질과 직결된다. 어떤 메서드가 서버의 상태를 건드리는지 아닌지를 아는 것만으로, 그 요청을 캐시해도 되는지 여러 번 반복해도 되는지를 판단할 수 있다.


흔히 쓰이는 메서드는 대여섯 개로 좁혀진다. 조회의 GET, 생성의 POST, 교체의 PUT, 부분 수정의 PATCH, 삭제의 DELETE 가 그 핵심이다. 여기에 응답 본문 없이 헤더만 확인하는 HEAD 와, 서버가 어떤 메서드를 허용하는지 묻는 OPTIONS 가 보조적으로 쓰인다. 이 정도만 확실히 알면 대부분의 상황을 다룰 수 있다.

조회를 담당하는 GET

가장 자주 쓰이는 메서드는 조회를 담당하는 GET 이다. 브라우저 주소창에 주소를 입력하거나 링크를 누르는 행위는 모두 GET 요청을 발생시킨다. 이 메서드는 자원을 가져올 뿐 서버의 상태를 바꾸지 않는다는 약속을 담고 있다. 그래서 몇 번을 반복해도 서버에 남는 변화가 없어야 한다.


GET 은 본문 없이 요청되는 것이 원칙이다. 조회에 필요한 조건은 경로 뒤의 질의 문자열에 실린다. 예를 들어 GET /articles?page=2&sort=recent 는 두 번째 쪽을 최신순으로 조회하겠다는 요청이다. 조회 조건이 주소에 그대로 드러나므로, 같은 주소를 공유하면 누구든 같은 조회 결과에 도달할 수 있다.


조회 조건을 주소에 담는 구조는 캐시와 잘 맞는다. 주소가 같으면 결과도 같으리라 기대할 수 있으므로, 중간의 캐시가 응답을 저장해 두었다가 재사용할 수 있다. GET 이 캐시의 주된 대상이 되는 이유가 여기에 있다. 조회성 요청을 캐시로 덜어 내면 서버의 부하가 크게 줄어든다.


다만 주소에 조건을 담는다는 점 때문에 민감한 정보를 GET 으로 보내면 안 된다. 주소는 기록에 남고 공유되며 로그에 찍힌다. 비밀번호나 개인 정보 같은 값을 질의 문자열에 실으면 여러 곳에 흔적이 남아 유출 위험이 커진다. 조회는 GET 으로 하되, 민감한 값을 담아야 한다면 다른 메서드를 써서 본문에 실어야 한다.


GET 이 서버 상태를 바꾸지 않는다는 약속은 반드시 지켜져야 한다. 조회처럼 보이는 요청이 몰래 데이터를 바꾸도록 설계하면, 검색 엔진의 수집기나 브라우저의 미리 읽기 기능이 그 요청을 자동으로 호출하면서 의도치 않은 변경이 일어난다. 상태를 바꾸는 동작은 반드시 변경성 메서드로 표현해야 한다.

생성을 담당하는 POST

새 자원을 만들거나 서버에 데이터를 제출할 때는 POST 를 쓴다. 회원 가입, 글쓰기, 댓글 등록처럼 서버에 무언가를 새로 추가하는 동작이 여기에 해당한다. POST 는 본문에 데이터를 실어 보내며, 그 데이터의 형식은 헤더의 형식 선언으로 알린다.


POST 는 서버의 상태를 바꾸는 대표적인 메서드다. 그래서 같은 POST 요청을 두 번 보내면 자원이 두 개 만들어질 수 있다. 결제 버튼을 두 번 눌러 주문이 중복되는 문제가 바로 이 성질에서 비롯된다. POST 는 반복 호출이 안전하지 않으므로, 중복을 막는 장치를 별도로 마련해야 한다.


중복을 막는 방법으로는 각 요청에 고유한 식별자를 붙여 서버가 이미 처리한 요청인지 확인하게 하는 방식이 있다. 클라이언트가 요청마다 유일한 값을 헤더에 실어 보내면, 서버는 같은 값이 다시 오면 새 자원을 만들지 않고 앞선 결과를 돌려준다. 결제나 주문처럼 중복이 치명적인 곳에서는 이런 보호 장치가 필수다.


POST 는 생성 외에도 다른 메서드로 표현하기 애매한 동작을 담는 자리로 쓰이기도 한다. 단순한 조회나 삭제로 나누기 어려운 복합적인 처리, 예컨대 여러 자원을 한꺼번에 다루는 작업이나 특정 절차를 실행하는 요청이 POST 로 표현된다. 규약이 정한 다른 메서드의 의미에 들어맞지 않을 때 POST 가 그 빈자리를 메운다.


다만 POST 를 남용해 모든 요청을 그것으로 처리하는 설계는 피해야 한다. 조회까지 POST 로 처리하면 캐시의 이점을 잃고, 메서드가 담는 의도의 정보가 사라진다. 각 동작을 그 성격에 맞는 메서드로 표현할 때 시스템의 의미가 분명해진다. POST 는 편리하지만 만능 도구로 쓰라고 만들어진 것이 아니다.

교체와 부분 수정, PUT과 PATCH

기존 자원을 통째로 교체할 때는 PUT 을 쓴다. PUT /articles/42 는 42번 글을 요청 본문에 담긴 내용으로 완전히 대체하겠다는 뜻이다. 교체이므로 본문에는 그 자원의 전체 상태가 담겨야 하며, 본문에 빠진 항목은 비워지는 것으로 해석될 수 있다.


PUT 의 중요한 성질은 여러 번 호출해도 결과가 같다는 것이다. 같은 내용으로 PUT 을 몇 번을 보내든 자원은 그 내용으로 한 번 바뀐 상태에 머문다. 이 성질 덕분에 응답을 받지 못해 요청을 다시 보내야 할 때도 안심할 수 있다. 재시도가 안전하다는 점은 불안정한 네트워크에서 큰 장점이 된다.


자원의 일부만 고치고 싶을 때는 PATCH 를 쓴다. 글의 제목만 바꾸거나 상태 값 하나만 갱신하는 경우, 전체를 다시 보내는 PUT 대신 바뀔 부분만 담은 PATCH 가 적합하다. 본문에는 변경할 항목만 실으면 되므로 데이터의 양이 줄고 의도도 분명해진다.


PUTPATCH 의 차이는 전체 교체와 부분 수정이라는 데 있다. 전체 상태를 보내면 PUT 이고 바뀔 부분만 보내면 PATCH 다. 이 둘을 혼동하면, 일부만 보낸 요청이 나머지 항목을 비워 버리는 사고가 난다. 어떤 메서드를 쓰느냐에 따라 서버가 본문을 해석하는 방식이 달라진다는 점을 기억해야 한다.


PATCH 는 부분 수정이라는 성격상 반복 호출의 안전성이 항상 보장되지는 않는다. 값을 특정 값으로 지정하는 PATCH 는 여러 번 호출해도 결과가 같지만, 기존 값에 무언가를 더하는 식의 PATCH 는 호출할 때마다 결과가 달라진다. 그래서 PATCH 를 설계할 때는 그 동작이 반복에 안전한지 따로 확인해야 한다.

삭제를 담당하는 DELETE

자원을 지울 때는 DELETE 를 쓴다. DELETE /articles/42 는 42번 글을 삭제하겠다는 요청이다. 의미가 분명하고 단순한 메서드이지만, 삭제라는 동작의 성격상 신중하게 다루어야 한다. 한번 지운 자원을 되돌리는 일은 대개 어렵기 때문이다.


DELETE 는 반복 호출에 대체로 안전하다. 이미 삭제된 자원을 다시 삭제하려 하면 서버는 더 이상 지울 것이 없다고 응답할 뿐, 추가로 바뀌는 상태는 없다. 그래서 삭제 요청을 재시도해도 자원이 없다는 상태에 머문다. 다만 서버가 이미 없는 자원에 대해 어떤 상태 코드를 돌려주느냐는 설계에 따라 다르므로, 그 응답을 클라이언트가 어떻게 해석할지 정해 두어야 한다.


실무에서는 자원을 실제로 지우는 대신 지워진 것으로 표시만 하는 방식을 자주 쓴다. 데이터를 물리적으로 없애지 않고 삭제 표시만 남기면, 실수로 지운 자원을 되살리거나 삭제 이력을 추적할 수 있다. 이 경우 겉으로는 DELETE 로 사라진 것처럼 보이지만 내부에는 데이터가 남아 있다. 삭제의 의미를 어디까지로 정할지는 서비스의 요구에 달렸다.


삭제는 권한 확인이 특히 중요한 동작이다. 조회는 잘못 노출되어도 정보가 새는 정도지만, 삭제는 잘못 허용되면 데이터가 사라진다. 그래서 DELETE 요청은 그 요청을 보낸 주체가 정말로 그 자원을 지울 자격이 있는지를 반드시 확인한 뒤에 처리해야 한다. 메서드의 의미가 강력할수록 그 앞의 검증도 엄격해야 한다.

안전성과 멱등성

메서드를 이해하는 핵심 열쇠는 안전성과 멱등성이라는 두 성질이다. 안전성은 그 메서드가 서버의 상태를 바꾸지 않는다는 성질이고, 멱등성은 같은 요청을 여러 번 보내도 결과가 한 번 보낸 것과 같다는 성질이다. 이 두 성질이 캐시와 재시도의 안전 여부를 결정한다.


조회를 담당하는 GET 은 안전하면서 동시에 멱등하다. 상태를 바꾸지 않으니 안전하고, 몇 번을 호출해도 같은 결과이니 멱등하다. 그래서 GET 은 캐시해도 되고 재시도해도 된다. HEAD 도 같은 성질을 가진다. 안전한 메서드는 자동화된 도구가 마음 놓고 호출할 수 있는 요청이다.


교체의 PUT 과 삭제의 DELETE 는 안전하지는 않지만 멱등하다. 상태를 바꾸므로 안전하지 않지만, 같은 요청을 반복해도 최종 상태가 같으므로 멱등하다. 이 멱등성 덕분에 응답을 받지 못한 요청을 다시 보내도 문제가 생기지 않는다. 반면 POST 는 안전하지도 멱등하지도 않아서, 반복 호출이 자원의 중복을 낳는다.


이 성질을 알면 재시도 전략을 안전하게 세울 수 있다. 멱등한 메서드는 응답이 없을 때 그냥 다시 보내면 되지만, 멱등하지 않은 POST 는 함부로 재시도하면 중복이 생긴다. 그래서 POST 를 재시도할 때는 앞서 말한 고유 식별자 같은 보호 장치를 반드시 함께 써야 한다. 성질을 알아야 안전한 자동화가 가능하다.

메서드 선택의 실무 기준

어떤 동작을 어떤 메서드로 표현할지는 그 동작의 성격을 따라 정하면 된다. 상태를 바꾸지 않는 조회는 GET, 새로 만드는 것은 POST, 통째로 바꾸는 것은 PUT, 일부만 고치는 것은 PATCH, 지우는 것은 DELETE 다. 동작의 의미와 메서드의 의미를 일치시키는 것이 가장 단순하고 견고한 기준이다.


이 원칙을 지키면 시스템이 스스로를 설명하게 된다. 요청 라인만 봐도 그 요청이 무엇을 하려는지 드러나고, 메서드의 성질만으로 캐시와 재시도의 안전 여부가 정해진다. 반대로 메서드의 의미를 무시하면 이 모든 이점이 사라지고, 시스템의 동작을 파악하려면 일일이 코드를 뜯어봐야 한다.


메서드 선택에서 가장 흔한 잘못은 조회에 변경을 섞는 것과 모든 것을 POST 로 처리하는 것이다. 앞의 잘못은 자동 호출로 인한 사고를 부르고, 뒤의 잘못은 메서드가 담는 의도의 정보를 지운다. 두 잘못 모두 당장은 동작하지만 시간이 지날수록 유지보수를 어렵게 만든다. 규약의 의미를 따르는 편이 결국 비용을 줄이는 길이다.


메서드를 제대로 다루면 문서가 없어도 협업이 수월해진다. 새로 합류한 사람이 요청의 메서드와 경로만 보고도 그 요청이 조회인지 생성인지 삭제인지를 곧바로 파악할 수 있기 때문이다. 반대로 메서드의 의미가 뒤죽박죽인 시스템은 모든 요청을 일일이 설명해야 하고, 그 설명이 빠지면 오해가 쌓인다. 메서드의 일관성은 결국 팀 전체의 이해 비용을 낮추는 규율이다. 규약을 지키는 일은 나 혼자 편하자는 것이 아니라 함께 일하는 사람들과의 약속을 지키는 일에 가깝다.


이번 편은 주요 메서드가 각각 무엇을 표현하고 어떤 성질을 약속하는지, 그리고 안전성과 멱등성이 왜 중요한지를 다루었다. 메서드가 요청의 의도를 밝힌다면, 그 요청의 결과를 밝히는 것은 응답의 상태 코드다. 다음 편에서는 그 상태 코드의 체계를 계열별로 파고들어, 각 숫자가 무엇을 뜻하는지 정리한다.