지금까지 다룬 요청과 응답, 메서드와 상태 코드, 헤더는 각각 웹의 부품이다. 이 부품들을 어떤 원칙으로 엮어 하나의 일관된 시스템을 만들 것인가는 또 다른 문제다. 그 물음에 대한 가장 널리 받아들여진 답이 레스트라는 설계 양식이다. 레스트는 새로운 기술이 아니라, HTTP가 이미 가진 성질을 최대한 살려 시스템을 설계하는 방식이다.
레스트는 흔히 오해받는다. 특정 형식의 데이터를 주고받으면 레스트라거나, 몇 가지 규칙만 지키면 레스트라는 식이다. 그러나 레스트의 본질은 형식이 아니라 사고방식에 있다. 이번 편은 레스트가 어떤 원칙 위에 서 있는지, 자원 중심의 사고가 무엇인지, 그리고 이 양식을 실무에 어떻게 적용하는지를 다룬다.
레스트라는 설계 양식
레스트는 웹이 어떻게 이렇게 거대하게 확장될 수 있었는지를 분석하는 데서 나온 설계 양식이다. 웹을 떠받치는 성질들을 추려, 그것을 시스템 설계의 원칙으로 정리한 것이다. 그래서 레스트를 따른다는 것은 웹의 검증된 성질을 그대로 활용한다는 뜻이 된다.
레스트는 몇 가지 제약의 묶음으로 정의된다. 클라이언트와 서버의 역할을 나누는 제약, 상태를 서버가 기억하지 않는 무상태 제약, 응답을 캐시할 수 있게 하는 제약, 그리고 자원을 다루는 방식을 통일하는 제약이 그 핵심이다. 이 제약들을 지키면 시스템이 확장성과 단순함을 얻는다.
중요한 점은 이 제약들이 이미 HTTP 안에 대부분 갖추어져 있다는 것이다. HTTP는 무상태이고, 캐시를 지원하며, 메서드로 통일된 조작을 제공한다. 그래서 HTTP의 성질을 거스르지 않고 순리대로 쓰기만 해도 레스트의 상당 부분이 자연스럽게 따라온다. 레스트는 규약을 억지로 비트는 것이 아니라 규약의 결을 따르는 것이다.
이 제약들이 함께 작동할 때 나오는 효과는 개별 제약의 합보다 크다. 무상태와 캐시 가능성이 맞물리면 응답을 안심하고 저장해 재사용할 수 있고, 통일된 조작과 무상태가 맞물리면 서버를 자유롭게 늘릴 수 있다. 하나하나의 제약은 사소해 보여도 서로 얽히면서 확장성이라는 큰 성질을 만들어 낸다. 레스트를 부분이 아니라 제약의 묶음으로 이해해야 하는 이유가 여기에 있다.
반대로 HTTP를 쓰면서도 그 성질을 거스르면 레스트에서 멀어진다. 모든 요청을 하나의 메서드로 처리하거나, 상태 코드를 무시하거나, 서버에 상태를 잔뜩 쌓아 두면, 겉으로는 웹을 쓰지만 레스트의 이점은 잃는다. 레스트는 지키기 어려운 규칙이 아니라, 규약의 의도를 존중하는 태도에 가깝다.
자원 중심의 사고
레스트의 출발점은 시스템을 자원의 모음으로 바라보는 것이다. 자원은 시스템이 다루는 의미 있는 대상이다. 글, 사용자, 주문, 댓글처럼 이름을 붙일 수 있는 모든 것이 자원이 될 수 있다. 레스트에서는 이 자원 하나하나에 고유한 주소를 부여하고, 그 주소를 통해 자원을 다룬다.
이 사고방식은 동작 중심의 사고와 대비된다. 동작 중심으로 설계하면 글을 가져오는 기능, 글을 만드는 기능, 글을 지우는 기능처럼 행위마다 별도의 이름을 붙이게 된다. 반면 자원 중심으로 설계하면 글이라는 자원 하나를 두고, 그것을 조회하고 만들고 지우는 일은 메서드로 표현한다. 대상이 중심에 서고 행위는 메서드로 밀려나는 것이다.
자원 중심의 사고가 주는 이점은 일관성이다. 모든 자원이 같은 방식으로 다루어지므로, 하나의 자원을 다루는 법을 익히면 다른 자원도 같은 방식으로 다룰 수 있다. 글을 조회하는 법을 알면 사용자를 조회하는 법도 짐작할 수 있다. 이 예측 가능성이 시스템 전체를 배우기 쉽게 만든다.
자원 중심의 사고는 시스템이 커질수록 진가를 드러낸다. 다루는 대상이 수십 종으로 늘어나도 각 대상이 같은 규칙으로 다루어지면 전체의 복잡도는 선형으로만 증가한다. 반면 동작 중심으로 기능마다 이름과 규칙이 제각각이면, 대상이 늘어날 때 익혀야 할 예외도 함께 불어난다. 자원이라는 일관된 틀이 복잡도의 증가 속도를 억누르는 셈이다.
자원을 잘 식별하는 것이 레스트 설계의 첫 관문이다. 시스템이 다루는 대상을 자원으로 추려 내고, 각 자원에 명확한 이름과 주소를 부여하는 작업이다. 이 단계에서 자원을 어떻게 나누느냐가 이후 설계의 뼈대를 결정한다. 자원의 경계가 흐리면 그 위에 얹히는 모든 것이 흔들린다.
메서드로 행위를 표현하기
자원을 정했다면, 그 자원에 가할 행위는 메서드로 표현한다. 조회는 조회 메서드로, 생성은 생성 메서드로, 수정은 수정 메서드로, 삭제는 삭제 메서드로 나타낸다. 자원의 주소는 그대로 두고, 무엇을 할지는 메서드가 결정한다. 이 분리가 레스트의 핵심 장치다.
이렇게 하면 주소가 명사로만 이루어진다. 주소에는 동작을 나타내는 동사가 들어가지 않고, 오직 대상인 자원만 담긴다. 무엇을 할지는 주소가 아니라 메서드가 밝히므로, 같은 주소가 메서드에 따라 여러 행위의 대상이 된다. 주소는 대상을 가리키고 메서드는 행위를 가리키는 역할 분담이다.
메서드의 성질을 활용하는 것도 중요하다. 앞서 다룬 안전성과 멱등성 같은 성질이 자원 조작에 그대로 적용된다. 조회는 안전한 메서드로 표현되어 캐시와 재시도가 안전하고, 교체와 삭제는 멱등한 메서드로 표현되어 반복해도 결과가 같다. 메서드를 의미에 맞게 쓰는 것만으로 이 성질들이 시스템에 스며든다.
행위를 메서드로 표현하기 어려운 경우도 있다. 단순한 조회나 삭제로 나누기 힘든 복합적인 동작, 여러 자원을 한꺼번에 다루는 작업이 그렇다. 이런 경우를 어떻게 다룰지는 레스트에서 늘 논쟁이 되는 지점이다. 무리하게 모든 것을 자원과 메서드로 욱여넣기보다, 규약의 의도를 살리는 선에서 실용적으로 판단하는 것이 낫다.
무상태와 자족적 요청
레스트는 무상태를 핵심 제약으로 삼는다. 서버가 요청 사이의 상태를 기억하지 않으므로, 각 요청은 그 자체로 처리에 필요한 모든 정보를 담아야 한다. 이 요청 하나만 놓고 봤을 때 서버가 완결적으로 처리할 수 있어야 한다는 것이 자족적 요청의 원칙이다.
이 원칙을 지키면 확장이 쉬워진다. 서버가 상태를 품지 않으니 요청이 어느 서버로 가도 똑같이 처리되고, 서버를 여러 대로 늘려 부하를 나눌 수 있다. 앞서 첫 편에서 다룬 무상태의 이점이 레스트 설계에서 그대로 살아난다. 무상태는 레스트가 확장성을 얻는 근본 이유다.
자족적 요청을 어기는 대표적인 잘못이 앞선 요청에 의존하는 설계다. 이전 요청에서 서버가 무언가를 기억해 두었다고 가정하고 다음 요청을 보내면, 그 가정이 깨지는 순간 시스템이 무너진다. 각 요청이 홀로 서게 만드는 것이 레스트의 규율이다. 상태가 필요하면 그것을 요청에 담거나 자원으로 표현해야 한다.
무상태는 서버의 부담을 클라이언트로 옮기는 면이 있다. 상태를 기억하지 않는 대신, 클라이언트가 매 요청에 필요한 정보를 실어야 하기 때문이다. 이 교환이 손해처럼 보일 수 있지만, 그 대가로 얻는 확장성과 단순함이 훨씬 크다. 서버를 가볍게 유지하는 것이 대규모 시스템에서는 결정적인 이점이 된다.
표현과 상태의 전달
레스트에서 클라이언트와 서버는 자원의 표현을 주고받는다. 클라이언트가 자원을 요청하면 서버는 그 자원의 현재 상태를 특정 형식으로 표현해 응답한다. 클라이언트가 자원을 바꾸려면 원하는 상태의 표현을 서버로 보낸다. 오가는 것은 자원 그 자체가 아니라 그 자원의 표현이다.
이 구분이 앞 편에서 다룬 협상과 이어진다. 하나의 자원이 여러 형식으로 표현될 수 있으므로, 클라이언트와 서버는 어떤 표현을 주고받을지 협상한다. 자원의 정체성은 주소로 고정되어 있고, 그 표현은 상황에 따라 달라진다. 레스트는 이 자원과 표현의 분리 위에 서 있다.
클라이언트는 받은 표현을 통해 자원의 상태를 파악하고, 다음에 무엇을 할 수 있는지를 안다. 잘 설계된 응답은 자원의 데이터뿐 아니라, 그 자원과 관련해 이동할 수 있는 다른 자원의 주소까지 함께 담기도 한다. 이렇게 하면 클라이언트가 주소를 미리 다 알지 못해도, 응답을 따라가며 시스템을 탐색할 수 있다.
다만 이런 탐색 가능한 설계는 이상에 가깝고, 실무에서는 필요한 만큼만 적용하는 경우가 많다. 모든 응답에 관련 주소를 빠짐없이 담는 것은 부담이 크기 때문이다. 레스트의 원칙을 이해하되, 어느 수준까지 적용할지는 시스템의 규모와 요구에 맞추어 정하는 실용적 판단이 필요하다.
통일된 인터페이스
레스트를 관통하는 정신은 통일된 인터페이스다. 모든 자원을 같은 방식으로 다루고, 같은 메서드로 조작하며, 같은 상태 코드로 결과를 알리는 것이다. 이 통일성 덕분에 시스템의 한 부분을 이해하면 다른 부분도 같은 규칙으로 다룰 수 있다.
통일된 인터페이스는 클라이언트와 서버를 느슨하게 묶는다. 둘이 같은 규약과 규칙만 공유하면, 서로의 내부 구현을 몰라도 통신할 수 있다. 서버가 내부를 바꾸어도 인터페이스만 유지되면 클라이언트는 영향을 받지 않는다. 이 느슨한 결합이 시스템을 오래 유지 가능하게 만든다.
통일성을 지키려면 절제가 필요하다. 특정 자원만 예외적으로 다르게 다루거나, 상황마다 다른 규칙을 만들면 통일성이 깨진다. 편의를 위한 작은 예외들이 쌓이면, 결국 시스템 전체가 일관성을 잃고 배우기 어려워진다. 통일된 인터페이스는 지속적으로 지켜 내야 하는 규율이다.
이 통일성이 주는 가장 큰 실무적 이득은 예측 가능성이다. 새로 합류한 사람이 시스템의 규칙을 한 번 익히면, 처음 보는 자원도 같은 방식으로 다룰 수 있다고 믿을 수 있다. 문서를 일일이 뒤지지 않아도 대략의 동작을 짐작할 수 있다. 이 예측 가능성이 협업의 비용을 크게 낮춘다.
통일된 인터페이스는 중간 요소들에게도 이점을 준다. 클라이언트와 서버 사이에 놓인 캐시나 중계 지점은 규약의 규칙만 알면 되지, 각 자원의 내부 의미까지 알 필요가 없다. 상태 코드와 메서드의 뜻이 표준으로 통일되어 있으므로, 중간 요소는 그 표준만 해석해 응답을 저장하거나 전달할 수 있다. 통일성이 시스템의 참여자를 늘려도 각자가 알아야 할 것은 최소로 묶어 두는 것이다.
레스트를 실무에 적용하기
레스트를 적용할 때 흔한 함정은 원칙을 교조적으로 따르려는 것이다. 모든 것을 완벽하게 자원과 메서드로 표현하려다 오히려 부자연스러운 설계가 나온다. 레스트는 목적이 아니라 수단이므로, 시스템을 더 단순하고 견고하게 만드는 선에서 적용하는 것이 옳다.
실무에서는 대부분의 자원을 레스트 원칙으로 깔끔하게 다루면서, 그 틀에 맞지 않는 소수의 동작은 실용적으로 처리하는 절충이 흔하다. 자원 중심의 일관성을 최대한 유지하되, 억지로 끼워 맞추기 어려운 부분은 무리하지 않는 것이다. 원칙의 정신을 지키되 형식에 얽매이지 않는 균형이 중요하다.
레스트의 진짜 가치는 일관성과 예측 가능성에 있다. 이 두 가지를 얻기 위해 원칙을 따르는 것이지, 원칙 자체를 위해 따르는 것이 아니다. 그래서 어떤 결정을 내릴 때 이것이 시스템을 더 일관되고 예측 가능하게 만드는지를 기준으로 삼으면, 대부분의 선택에서 올바른 방향을 찾을 수 있다.
레스트를 절대적인 정답으로 떠받들 필요도 없다. 자원과 메서드로 자연스럽게 표현되는 도메인이 있는가 하면, 절차나 계산이 중심이 되어 자원의 틀이 잘 맞지 않는 도메인도 있다. 후자에까지 레스트를 강요하면 오히려 설계가 뒤틀린다. 규약이 제공하는 도구를 알고, 그것이 잘 맞는 곳에 쓰는 분별이 원칙을 외우는 것보다 중요하다. 레스트는 판단을 돕는 틀이지 판단을 대신하는 규칙이 아니다.
이번 편은 레스트가 어떤 제약 위에 서 있는지, 자원 중심의 사고와 통일된 인터페이스가 무엇인지, 그리고 이를 실무에 적용하는 태도를 다루었다. 레스트가 자원을 중심에 둔다면, 그 자원을 실제로 가리키는 주소를 어떻게 지을지가 남은 과제다. 다음 편에서는 자원과 주소를 설계하는 구체적인 원칙을 살펴보며 이 시리즈를 맺는다.