앞 편에서 레스트가 시스템을 자원의 모음으로 바라본다고 했다. 그렇다면 그 자원을 실제로 가리키는 것은 무엇인가. 주소다. 자원마다 고유한 주소를 부여하고 그 주소로 자원을 다루는 것이 레스트의 뼈대이므로, 주소를 어떻게 짓느냐가 곧 시스템의 얼굴을 결정한다. 이번 편은 자원을 가리키는 주소를 설계하는 원칙을 다룬다.


주소 설계는 사소해 보여서 자주 미뤄진다. 일단 동작하게 만든 뒤 주소는 나중에 다듬으면 된다고 여기기 쉽다. 그러나 한 번 공개된 주소는 여러 클라이언트가 참조하기 시작하므로 바꾸기가 어렵다. 초기에 세운 주소 규칙이 시스템의 수명 내내 따라다닌다. 이번 편은 주소가 왜 설계의 문제인지, 어떤 규칙으로 지어야 하는지, 그리고 흔히 저지르는 잘못이 무엇인지를 순서대로 짚는다.

주소가 곧 설계다

주소는 단순한 문자열이 아니라 시스템의 구조를 밖으로 드러내는 지도다. 잘 지어진 주소의 묶음을 훑어보면, 그 시스템이 어떤 자원을 다루고 자원들이 어떻게 얽혀 있는지가 한눈에 읽힌다. 주소가 곧 시스템의 설명서 역할을 하는 것이다. 반대로 주소가 무질서하면 시스템의 구조도 무질서하다는 신호인 경우가 많다.


주소를 설계로 다루어야 하는 이유는 그것이 오래 남기 때문이다. 내부 구현은 얼마든지 바꿀 수 있지만, 겉으로 노출된 주소는 클라이언트와 맺은 약속이라 함부로 바꿀 수 없다. 한 번 공개된 주소를 바꾸면 그것을 참조하던 모든 클라이언트가 깨진다. 그래서 주소는 내부 사정과 무관하게 안정적으로 유지되어야 하는 계약에 가깝다.


이 안정성 때문에 주소는 내부 구조를 그대로 베껴서는 안 된다. 데이터베이스의 표 이름이나 내부 코드의 함수 이름을 주소에 그대로 노출하면, 내부를 바꿀 때마다 주소가 흔들린다. 주소는 내부가 어떻게 생겼든 클라이언트에게 안정적으로 보여야 할 대상의 이름이어야 한다. 내부와 주소를 분리해 두는 것이 변화에 견디는 설계다.


주소 설계의 첫 질문은 이 시스템이 다루는 자원이 무엇인가이다. 자원을 명확히 추려 내면 주소는 그 자원에 이름을 붙이는 일로 자연스럽게 이어진다. 자원의 경계가 흐린 채 주소부터 지으면, 하나의 주소가 여러 개념을 뒤섞어 가리키게 되어 나중에 손댈 수 없게 된다. 좋은 주소는 좋은 자원 식별에서 나온다.

명사로 짓는 경로

주소의 경로에는 동사가 아니라 명사가 들어간다. 무엇을 할지는 메서드가 밝히므로, 주소에는 행위의 대상인 자원만 담으면 된다. 글을 가져오는 주소와 글을 지우는 주소가 따로 있는 것이 아니라, 글을 가리키는 하나의 주소가 있고 조회와 삭제는 메서드로 구분된다. /articles/42 라는 주소는 42번 글이라는 대상만 가리킨다.


경로에 동사가 스며들면 주소가 금세 지저분해진다. 글을 가져오는 주소, 글을 갱신하는 주소, 글을 게시하는 주소를 각각 동사로 지으면, 자원 하나에 주소가 여러 개 생기고 규칙도 사라진다. 이런 주소는 개수가 무한정 늘어나며, 새 동작이 생길 때마다 새 주소를 발명해야 한다. 명사로 통일하면 자원의 수만큼만 주소가 존재한다.


명사는 대개 복수형으로 짓는다. /articles 는 글의 묶음을 가리키고, 그 아래 /articles/42 는 그 묶음 속 하나를 가리킨다. 묶음과 개별을 복수형과 식별자로 구분하는 이 규칙은 단순하지만 강력하다. 주소만 봐도 이것이 여럿을 가리키는지 하나를 가리키는지가 즉시 읽힌다. 규칙이 일관되면 클라이언트가 주소를 짐작할 수 있다.


단어의 표기도 규칙을 정해 통일해야 한다. 여러 단어가 이어지는 이름을 어떻게 이을지, 대문자와 소문자를 어떻게 쓸지를 처음에 정하고 끝까지 지킨다. 어떤 주소는 이 방식으로, 다른 주소는 저 방식으로 지으면, 클라이언트가 매번 정확한 표기를 확인해야 한다. 사소해 보이는 표기의 일관성이 실제로는 사용 편의를 크게 좌우한다.

계층과 관계의 표현

자원들 사이에는 관계가 있다. 어떤 글에는 여러 댓글이 달리고, 어떤 사용자는 여러 주문을 가진다. 이 소속 관계를 주소의 계층으로 표현한다. /articles/42/comments 는 42번 글에 속한 댓글의 묶음을 가리킨다. 경로가 깊어지는 것이 소속의 깊이를 그대로 드러낸다.


계층 구조는 관계를 직관적으로 보여 주지만, 지나치게 깊어지면 오히려 다루기 어렵다. 소속의 소속의 소속까지 경로에 담으면 주소가 길고 복잡해진다. 대개 한두 단계의 소속까지만 경로로 표현하고, 그 이상은 다른 방식으로 접근하게 하는 것이 실용적이다. 계층은 관계를 드러내는 수단이지 모든 관계를 담는 그릇이 아니다.


깊은 계층 대신 개별 자원에 직접 접근하는 길을 함께 두기도 한다. 댓글을 글에 소속시켜 접근할 수도 있지만, 댓글 자체가 고유한 식별자를 가진다면 /comments/108 처럼 곧장 가리킬 수도 있다. 목록을 볼 때는 소속을 통해 접근하고, 개별 자원을 다룰 때는 직접 접근하는 두 길을 상황에 맞게 쓴다. 어느 하나만 고집할 필요는 없다.


관계 중에는 소속이 아니라 참조인 것도 있다. 한 자원이 다른 자원을 가리키기만 할 뿐 소유하지는 않는 관계다. 이런 관계까지 경로의 계층으로 욱여넣으면 주소가 왜곡된다. 소속이 아닌 관계는 경로가 아니라 응답 안에서 상대의 주소를 담아 표현하는 편이 자연스럽다. 계층은 소속을 표현하는 데 쓰고, 참조는 다른 방식으로 나타내는 구분이 필요하다.


계층의 깊이를 정하는 기준은 그 소속이 접근에 꼭 필요한가이다. 댓글을 다루려면 어느 글에 속했는지가 늘 필요하다면 소속을 경로에 담는 편이 자연스럽고, 개별 식별자만으로 충분히 접근된다면 굳이 소속을 경로에 넣지 않아도 된다. 이 판단을 자원마다 해 두면 경로가 필요 이상으로 길어지지 않는다. 깊이는 관계의 실제 무게에 맞추어 정하는 것이지, 관계가 있다고 해서 무조건 경로에 새기는 것이 아니다.

컬렉션과 개별 자원

레스트 주소는 크게 두 종류로 나뉜다. 여럿을 가리키는 컬렉션 주소와 그중 하나를 가리키는 개별 주소다. /articles 는 글 전체의 묶음이고 /articles/42 는 그 안의 한 글이다. 이 두 종류에 메서드가 붙는 방식이 다르므로, 둘을 명확히 구분하는 것이 설계의 기본이다.


컬렉션 주소에 조회 메서드를 쓰면 목록을 얻고, 생성 메서드를 쓰면 그 묶음에 새 항목을 추가한다. 새 글을 만들 때 개별 주소가 아니라 컬렉션 주소로 요청하는 이유가 여기에 있다. 아직 식별자가 없는 새 자원은 개별 주소로 가리킬 수 없으므로, 그것이 속할 묶음에 추가를 요청하는 것이다. 서버가 식별자를 부여하고 새 자원의 주소를 응답으로 알린다.


개별 주소에 조회 메서드를 쓰면 그 하나를 얻고, 교체 메서드를 쓰면 그 하나를 통째로 바꾸며, 삭제 메서드를 쓰면 그 하나를 지운다. 개별 주소는 이미 식별자가 정해진 특정 자원을 가리키므로 그 자원 하나에만 작용한다. 컬렉션에 작용하는 조작과 개별에 작용하는 조작을 주소의 종류로 갈라 두면 혼동이 없다.


이 구분이 흐려지면 이상한 주소가 생긴다. 개별 주소에 목록을 얻으려 하거나 컬렉션 주소로 특정 하나를 지우려 하면, 주소와 동작의 의미가 어긋난다. 주소가 무엇을 가리키는지와 메서드가 무엇을 하는지가 맞물려야 요청의 의도가 분명해진다. 두 축이 어긋난 요청은 읽는 사람을 혼란스럽게 한다.

식별자의 선택

개별 자원을 가리키려면 식별자가 필요하다. 흔히 쓰는 것은 내부에서 순차적으로 매기는 숫자다. 짧고 다루기 쉽지만, 전체 개수가 밖으로 드러나고 다음 자원의 주소가 예측된다는 약점이 있다. 순서가 노출되어 곤란한 자원에는 이 방식이 적절하지 않을 수 있다.


대안은 규칙성이 없는 긴 식별자를 쓰는 것이다. 순서를 짐작할 수 없고 충돌 없이 분산해 생성할 수 있다는 장점이 있지만, 길고 사람이 읽기 어렵다는 단점이 따른다. 어떤 식별자를 쓸지는 자원의 성격에 달렸다. 순서가 드러나도 괜찮은 자원과 감춰야 하는 자원을 구분해 방식을 정한다.


사람이 읽기 좋은 이름을 식별자로 쓰는 방식도 있다. 글의 제목을 다듬어 주소에 넣으면 주소만 보고도 내용을 짐작할 수 있고 검색에도 유리하다. 다만 제목이 바뀌면 주소가 흔들리므로, 이 이름을 바꿀 수 있게 할지 고정할지를 정해 두어야 한다. 읽기 좋음과 안정성 사이에서 균형을 잡는 선택이다.


어떤 식별자를 고르든 지켜야 할 것은 그것이 자원을 유일하게 가리켜야 한다는 점이다. 하나의 식별자가 시점에 따라 다른 자원을 가리키면 주소의 신뢰가 무너진다. 한 번 어떤 자원에 부여된 식별자는 그 자원에만 붙어 있어야 하고, 자원이 사라진 뒤 그 식별자를 다른 자원에 재사용하는 일은 피해야 한다. 식별자의 유일함이 주소의 신뢰를 떠받친다.

쿼리와 경로의 구분

주소에는 경로 외에 물음표 뒤에 붙는 쿼리 부분이 있다. 이 둘의 역할은 다르다. 경로는 자원 자체를 가리키고, 쿼리는 그 자원을 어떻게 걸러 내거나 어떤 방식으로 보여 줄지를 지정한다. 무엇을 가리키는지는 경로에, 그것을 어떻게 다룰지는 쿼리에 담는 것이 원칙이다.


컬렉션을 다룰 때 이 구분이 특히 유용하다. /articles 는 글의 묶음을 가리키는 경로이고, 그 뒤에 붙는 쿼리로 특정 조건에 맞는 글만 걸러 내거나 순서를 정하거나 몇 개씩 나눌지를 지정한다. 걸러 내는 조건이 달라져도 가리키는 대상은 여전히 글의 묶음이므로, 그 조건은 경로가 아니라 쿼리에 두는 것이 맞다.


이 구분을 어기면 주소가 폭발한다. 조건마다 새 경로를 만들면, 걸러 내는 방법의 수만큼 주소가 늘어난다. 최신 글의 경로, 인기 글의 경로, 특정 사용자의 글 경로를 따로 만드는 대신, 하나의 컬렉션 경로에 쿼리로 조건을 얹으면 주소는 하나로 유지된다. 경로는 대상을, 쿼리는 조건을 맡는 분업이 주소를 단순하게 지킨다.


다만 쿼리로 표현할지 경로로 표현할지가 늘 자명하지는 않다. 어떤 조건이 사실상 별개의 자원을 만들어 낸다면 경로가 나을 수 있고, 같은 자원을 걸러 보는 것에 불과하다면 쿼리가 맞다. 이 판단의 기준은 그것이 새로운 대상인가 아니면 같은 대상의 다른 시야인가이다. 대상이 바뀌면 경로, 시야가 바뀌면 쿼리로 가르면 대개 어긋나지 않는다.

좋은 주소가 오래간다

좋은 주소의 조건을 한마디로 줄이면 예측 가능성이다. 한 자원의 주소를 알면 관련된 다른 자원의 주소를 짐작할 수 있어야 한다. 글의 주소를 보고 그 글의 댓글 주소를 떠올릴 수 있다면, 그 주소 체계는 규칙이 살아 있는 것이다. 규칙이 있으면 문서를 뒤지지 않아도 주소가 손에 잡힌다.


주소는 한 번 정하면 오래간다는 전제로 지어야 한다. 나중에 바꿀 생각으로 대충 지으면, 그 주소가 이미 여러 곳에 퍼진 뒤라 바꿀 수 없게 된다. 어쩔 수 없이 주소를 바꿔야 할 때는 옛 주소가 새 주소를 가리키도록 이어 주어 기존 참조가 깨지지 않게 해야 한다. 이 부분은 뒤의 버전 관리 편에서 더 다룬다.


주소를 지을 때 유행하는 세부 규칙에 지나치게 매달릴 필요는 없다. 완벽한 단 하나의 규칙을 찾기보다, 하나의 일관된 규칙을 정해 끝까지 지키는 편이 낫다. 어떤 방식을 택하든 그것이 시스템 전체에 고르게 적용되면, 클라이언트는 그 규칙을 한 번 익혀 어디에나 적용할 수 있다. 완벽함보다 일관성이 실무에서 더 큰 이득을 준다.


이번 편은 주소가 왜 설계의 문제인지, 명사와 계층으로 자원을 어떻게 표현하는지, 컬렉션과 개별을 어떻게 구분하는지, 식별자와 쿼리를 어떻게 다루는지를 살펴보았다. 주소를 잘 지어 컬렉션을 열어 두면, 곧바로 마주치는 현실적인 문제가 있다. 묶음에 담긴 자원이 수천 수만으로 불어날 때 그것을 한 번에 다 내어 줄 수는 없다는 것이다. 다음 편에서는 이 큰 컬렉션을 나누어 전달하는 페이지네이션 설계를 다룬다.