웹 API 설계: 놓치기 쉬운 핵심 - URL 설계
URL 설계
URL에서 명사는 좋고, 동사는 나쁘다
REST의 데이터 지향 모델의 결과로, 모든 URL은 어떤 '대상'을 식별합니다. 즉, API 클라이언트 프로그래머가 읽고 쓸 수 있도록 설계된 URL과 쿼리 URL은 사물(대상)을 나타내는 자연어의 품사인 '명사'로 구성되어야 한다는 의미입니다.
잘 알려진 URL
위의 예에서, 알려진 개들의 컬렉션에 대한 URL은 https://dogtracker.com/dogs입니다. API에서는 최소한 하나 이상의 잘 알려진 URL을 게시해야 하며, 그렇지 않으면 클라이언트가 시작할 수 없습니다. 우리는 아마도 https://dogtracker.com/owners 같은 URL을 통해 알려진 소유자에 대한 또 다른 URL을 제공할 수도 있습니다.
이론적으로는 https://dogtracker.com/에서 모든 유용한 정적 엔티티의 URL을 포함한 단일 엔티티를 게시하여 단일 URL로도 충분할 수 있습니다. 이렇게 하는 것은 API 전체를 발견 가능하고 크롤링 가능하게 만들어주므로 좋은 관행입니다. 그러나 클라이언트가 매번 그 루트로 이동하는 것은 번거로울 수 있기 때문에, 애플리케이션과 함께 제공된 삭제 불가능한 모든 정적 엔티티의 URL은 결국 클라이언트에게 잘 알려지게 될 것입니다. 우리가 사용하는 https://dogtracker.com/dogs와 같은 URL은 기억하기 쉽고, API를 종료하거나 도메인 이름을 팔거나 잃지 않는 한 안정적인 URL이 될 가능성이 큽니다.
엔티티 URL 설계
클라이언트를 위해 개별 엔티티의 URL 형식을 정의할 필요는 없으며, 일반적으로 잘 설계된 웹 API에서는 클라이언트가 개별 엔티티의 URL을 조각으로부터 만들어낼 필요가 없어야 합니다(이 문장을 다시 읽어보세요—중요한 내용입니다). 개가 생성되면 새로운 개의 URL이 클라이언트에게 반환되며, 클라이언트는 그것을 기억하거나 저장하여 나중에 참조할 수 있습니다. https://dogtracker.com/dogs의 [부분 집합]을 검색하면 잊어버리거나 알지 못했던 개의 URL을 복구할 수 있습니다. 따라서, 다음과 같은 형식의 개 URL을 사용하는 것이 적절할 것입니다:
https://dogtracker.com/ZG9n;a8098c1a
컴퓨터는 이런 형태의 URL을 처리하는 데 어려움이 없지만, 인간에게는 이러한 URL이 어렵게 느껴집니다. API는 컴퓨터가 사용하도록 설계되었지만, 그 컴퓨터는 인간에 의해 프로그래밍됩니다. 이러한 이유로, 사람들에게 더 편리하고 다음과 같은 형태로 보이는 엔티티 URL을 정의하는 것이 더 일반적이며, 보통 더 바람직합니다.
https://dogtracker.com/dogs/a8098c1a
이 URL은 특별한 컨텍스트가 없는 클라이언트에 의해 불투명한 URL로 여전히 사용될 수 있지만, 만약 인간 사용자가 dogtracker.com에서 개에 대한 컨텍스트를 이미 알고 있다면, 그 인간은 a8098c1a만 기억하거나 인식하면 됩니다. 나머지는 알려진 컨텍스트에서 구성할 수 있습니다. 이는 사람들에게 유용한데, 사람들은 컨텍스트를 잘 이해하지만 임의의 긴 문자열을 파싱하는 데는 능숙하지 않기 때문입니다. 불행히도, 많은 API는 단순히 이러한 컨텍스트 지식을 수용하는 대신 항상 이와 같은 컨텍스트 지식을 요구합니다.
영구 링크
서버가 리소스의 표현에 넣는 모든 링크는 북마크할 수 있고, 클라이언트에 의해 저장되어 나중에 사용할 수 있습니다. 이는 링크를 안정적으로 만들기 위해 노력해야 함을 의미합니다.
안정적인 URL을 설계하는 방법에 대한 좋은 조언이 웹에 있습니다. 다음은 팀 버너스-리(Tim Berners-Lee)가 쓴 좋은 예로, “생성 날짜 이후, 이름에 어떤 정보를 넣는 것은 어느 방식으로든 문제를 일으키는 것을 요청하는 것”이라는 주장을 포함하고 있습니다.
이 조언을 따르면 링크에 사용되는 URL은 사람들에게 다소 불친절하게 되는 결과가 발생합니다. 변경될 수 있는 정보를 포함하지 않도록 해야 하기 때문에, 대신 무작위 문자의 긴 문자열을 포함하는 경향이 있습니다. 예를 들어, 구글에서 제공하는 다음과 같은 URL이 있습니다:
https://docs.google.com/document/d/1YADQAXN2sqyC20Akblp75GUccnAScVSVI7GZk5M-TRo
구글 문서(Google Docs)와 마이크로소프트 원드라이브(Microsoft OneDrive)와 같은 웹사이트의 인기는 아마도 이러한 URL에 대한 저항을 줄였을 것입니다. 그러나 모든 URL이 인간에게 불친절할 필요는 없다는 점도 이해하는 것이 중요합니다. 이 문서 뒤편에서 쿼리 URL에 대한 논의에서 이를 확인할 수 있습니다.
위의 논의로 미루어 보아, 링크에 사용되는 URL의 이상적인 형식은 다음과 같을 것이라고 생각할 수 있습니다:
https://dogtracker.com/{uuid}
영구 링크(URL)는 이를 발급하는 서버의 이익을 위해 전적으로 설계된 것으로, 이를 사용하는 클라이언트를 위한 것이 아닙니다. 일반적인 필요 중 하나는 들어오는 GET 요청을 이를 처리할 수 있는 구현 단위로 라우팅하는 것입니다. 이 때문에 URL이 리소스의 식별성을 인코딩하는 것만으로는 충분하지 않습니다. 이를 처리할 수 있는 구현 단위의 식별성도 포함되어야 합니다. 구현 단위는 시간이 지남에 따라 변경되므로, URL 설계자에게 필요한 것은 나중에 처리 단위에 유연하게 매핑될 수 있는 식별자입니다. 이러한 식별자로 많이 사용되는 데이터 항목은 리소스의 주요 유형으로, 이는 변경 불가능한 것으로 가정됩니다. 그래서 링크에 사용되는 영구 링크의 일반적인 형식은 다음과 같습니다:
https://dogtracker.com/{type}/{uuid}
타입과 UUID를 슬래시로 구분할 특별한 이유는 없습니다. 다른 특수 문자를 사용하거나 구분자 없이 고정 길이 문자열을 타입으로 사용하는 것도 똑같이 잘 작동하지만, 슬래시는 전통적인 구분자입니다. 슬래시에 특별한 의미를 부여하는 유용한 표준 구현 소프트웨어 조각들이 있기 때문에, 타입과 리소스 ID를 슬래시로 구분하는 것은 실용적인 선택입니다.
평면적인 웹
계층은 어디에나 존재하며, 계층은 우리가 세상을 생각하는 데 중요한 부분입니다. 예를 들어, 직원은 부서에 소속되고, 부서는 사업 단위에 속하며, 사업 단위는 회사에 속합니다. 따라서 이러한 계층을 URL에 인코딩하는 것은 매우 유혹적입니다. 쿼리 URL의 경우, 계층(및 기타 관계)을 통해 탐색 경로를 나타내는 쿼리는 사물을 찾는 데 매우 유용할 수 있으므로 좋은 아이디어일 수 있습니다. 그러나 서버에 저장되고 클라이언트에 의해 북마크될 수 있는 링크 URL의 경우, URL에 계층을 인코딩하는 것은 좋지 않은 아이디어입니다. 계층은 생각보다 안정적이지 않으며, 이를 URL에 인코딩하면 계층을 재구성할 수 없거나 재구성할 때 지속적인 링크가 끊어질 수 있습니다.
이름 변경 딜레마에 대한 해결책
다음은 많은 API 디자이너들이 겪는 상황입니다. 당신은 이름과 같은 사람 친화적인 식별자를 가진 리소스를 가지고 있습니다. 또한, 기계에서 생성된 UUID와 같은 사람에게는 다소 친숙하지 않은 식별자를 가질 수 있습니다. 당신은 리소스의 URL이 사람 친화적이기를 원합니다. 왜냐하면 URL이 API 경험의 중요한 부분이라는 것을 이해하고 있기 때문입니다. 그래서 당신은 딜레마에 직면하게 됩니다:
사람 친화적인 식별자를 URL에 넣거나 (또는 다른 방법으로 사람 친화적인 식별자를 사용하여 리소스를 지속적으로 참조하면) 사람 친화적인 URL을 얻을 수 있지만, 리소스를 이름 변경할 수는 없습니다. 왜냐하면 그렇게 하면 모든 참조가 깨지기 때문입니다. 반면, 기계에서 생성된 식별자를 URL에 넣거나 (또는 다른 방법으로 기계에서 생성된 식별자를 사용하여 리소스를 지속적으로 참조하면) 리소스를 자유롭게 이름 변경할 수 있지만, 그 경우 사람에게 친숙하지 않은 URL을 얻게 됩니다.
이 문제에 대한 해결책은 링크에서 하나의 URL을 사용하고 쿼리에 대한 URI 템플릿에서는 다른 URL을 제공하는 것입니다. 앞서 보여준 Joe Carraclough의 예를 들어보겠습니다. Joe의 이름만으로는 아마도 고유하지 않을 것입니다. 하지만 Joe에게 고유한 이름을 선택하도록 할 수 있습니다. 예를 들어, Joe가 JoeMCarraclough를 선택했다고 가정해 봅시다. 이는 단순히 Joe Carraclough보다 사람 친화적이지는 않지만, 기계에서 생성된 ID인 e9cdcf7a-25b3-11e5-9aec-34363bd0ac10보다는 훨씬 낫습니다. 그런 다음, 개인에 대한 쿼리를 위한 URI 템플릿을 다음과 같은 형태로 정의합니다
https://dogtracker.com/person/{name}
이로 인해 우리는 Joe를 다음과 같은 URL로 주소 지정할 수 있습니다:
https://dogtracker.com/person/JoeMCarraclough
이 URL은 Joe를 찾는 데 매우 유용하며, 우리의 API에 포함하기에 매력적인 URL입니다. 하지만 우리는 이 URL을 링크에 사용하고 싶지 않습니다. 이름은 고유하게 만들 수 있지만, 변경이 가능해야 하므로 영구 링크에는 바람직하지 않습니다. Joe에 대한 영구 링크를 표현하고자 한다면, 다음과 같이 보일 수 있습니다:
{
"id": "12345678",
"kind": "Dog"
"name": "Lassie",
"furColor": "brown",
"Owner": "https://dogtracker.com/person/e9cdcf7a-25b3-11e5-34363bd0ac10"
}
이 URL은 아름답지는 않지만, 사용자가 링크에서 URL을 파싱할 수 있는 것이 중요하지 않기 때문에 그리 문제가 되지 않습니다. API 사용자에게 더 잘 보이는 URL 형식은 URI 템플릿에서 파생된 쿼리 URL입니다.
Joe가 그의 여자친구와 결혼하고 새 아내의 성인 Smith를 채택하기로 결정하면, Joe의 이름을 바꾸는 데 아무런 어려움이 없습니다. Joe에 대한 모든 영구 참조는 Joe의 기계 생성된 영구 링크를 사용하므로, 그 부분은 변경할 필요가 없습니다. URI 템플릿을 사용하여 Joe를 쿼리로 찾으려면, 그의 새 이름을 다음과 같이 제공하면 됩니다:
https://dogtracker.com/persons/JoeSmith44
위의 접근 방식에서 다음 두 URL은 실제로 서로 다른 리소스를 식별합니다:
https://dogtracker.com/persons/e9cdcf7a-25b3-11e5-34363bd0ac10
https://dogtracker.com/persons/JoeMCarraclough
첫 번째 URL은 특정한 사람을 식별하고, 두 번째 URL은 현재 "JoeMCarraclough"라는 이름을 가진 사람을 식별합니다. 이 두 리소스는 특정 시점에서는 동일해 보일 수 있지만, 시간이 지나면서 달라질 수 있습니다. 이 두 리소스 간의 관계는 다음 리소스들 간의 관계와 유사합니다
http://dbpedia.org/page/Donald_Trump
http://dbpedia.org/page/Current_President_Of_the_United_States
이 두 URL이 실제로는 매우 다른 것을 식별하지만, 트럼프의 임기가 끝날 때까지는 동일한 대상을 식별하는 것처럼 보일 것입니다. 이는 오바마의 임기가 2017년 1월 20일 정오에 끝날 때까지, 두 번째 링크가 이전에 http://dbpedia.org/page/Barack_Obama 를 식별하는 것처럼 보였던 것과 유사합니다.
이름 변경을 관리하는 또 다른 방법으로는 별칭(aliases)을 사용하는 방법이 있습니다. 이 방법에서는 리소스의 이름을 URL에 포함시키고, 이름이 변경되면 새 이름을 포함한 새로운 별칭 URL을 추가합니다. 같은 엔티티는 이전 이름이 포함된 URL이나 새로운 이름이 포함된 URL을 동시에 사용할 수 있게 됩니다. 별칭을 도입하는 방법은 리디렉션을 사용하는 방식과 결합될 수 있으며, 보통 이전의 별칭들은 최신 별칭으로 리디렉션됩니다. 이렇게 리디렉션을 사용하는 방식은 특정 URL을 리소스의 표준 URL(정규 URL)로 설정하며, 이는 여러 동일 리소스에 대해 다수의 별칭을 두는 것보다 선호될 수 있습니다. 최신 별칭으로 리디렉션하는 전략을 선택할 경우, 정규 URL은 시간이 지나면서 변경될 수 있으며, 이는 일부 시나리오에서 문제를 일으킬 수 있습니다. GitHub는 리포지토리 이름을 변경할 수 있도록 별칭과 리디렉션을 사용하는데, 이 설계의 결과로 이전 이름은 재사용되지 않고, 해당 이름에 대응하는 URL은 영구적으로 리소스와 연결됩니다. 이전 이름을 유지하는 것은 일반적으로 문제가 되지 않지만, 구현상 리소스의 이전 이름과 현재 이름을 모두 저장해야 합니다.
타입 안정성
이전 예시에서 우리는 https://dogtracker.com/person/e9cdcf7a-25b3-11e5-34363bd0ac10 를 조의 안정적이고 영구적인 링크 URL로 사용했습니다. 이는 person이라는 타입이 안정적인 속성이라는 가정에 기반한 것입니다. 이 예시에서는 아마 괜찮겠지만, 타입에 대해서는 신중해야 합니다. 어떤 타입들은 다른 타입들보다 더 안정적이기 때문입니다. 사람은 개나 쥐로 변하지 않지만(비유적으로는 그럴 수 있을지 몰라도), 사냥꾼은 먹이가 되기도 하고, 정치인은 로비스트가 되며, 클라이언트는 서버이기도 합니다. 이 예시는 단일 리소스가 둘 이상의 타입을 가질 수 있다는 사실을 보여줍니다. person이라는 타입이 영구 링크 URL에 나타나는 유일한 이유는 서버가 들어오는 요청을 구현의 적절한 부분으로 라우팅할 수 있도록 하기 위해서입니다. 따라서 이 주제에 대해 생각할 때는 리소스와 구현 핸들러 간의 현재 및 미래의 매핑 관점에서 생각해야 하며, 클라이언트에게 링크 URL은 불투명한 것으로 간주되어야 합니다.
쿼리 URL 설계
이 책의 앞부분에서 우리는 HTTP를 사용하는 것만으로도 API가 얼마나 나아질 수 있는지 강조했습니다. 그 이유는 API에서 고유하게 배워야 할 부분을 줄일 수 있기 때문입니다. HTTP를 사용하는 것은 데이터베이스 관리 시스템(DBMS)을 사용하는 것과 유사합니다. 일단 DBMS가 제공하는 API를 배우면, 그 DBMS에서 호스팅되는 모든 데이터베이스에 대해 동일한 지식을 반복해서 사용할 수 있습니다. 새로운 데이터베이스에 접근할 때 배워야 할 것은 데이터의 스키마일 뿐, 데이터베이스에 접근하기 위한 API는 아닙니다. DBMS의 표준 API는 데이터를 생성, 삭제, 업데이트하는 기본적인 작업을 할 수 있게 해주며, 또한 쿼리를 사용해 엔티티를 찾을 수 있게 합니다. 일단 DBMS의 쿼리 언어를 배우면, 이미 그 지식을 활용해 어떤 데이터베이스든 쿼리를 작성할 수 있습니다.
자원 설계에 대한 섹션과 이전 섹션에서, 링크에 사용되는 퍼머링크 URL과 상태에 포함된 데이터를 바탕으로 자원을 찾을 수 있는 쿼리 URL을 함께 사용하는 것의 가치를 논의했습니다. 쿼리 URL은 HTTP에서 DBMS의 쿼리와 동일한 역할을 합니다.
API 설계자가 그들의 API를 위해 URI 템플릿을 정의할 때, 사실상 API를 위한 맞춤형 쿼리 언어를 설계하는 것입니다. 설계자는 이 쿼리 언어를 정의할 때 상당한 자유를 가지고 있지만, 이 자유를 더 많이 활용할수록 API 클라이언트는 해당 URI 템플릿의 특성을 배우느라 더 많은 부담을 느끼게 됩니다. 이 섹션의 목표는 쿼리 URL을 규칙적이고 예측 가능하게 설계하는 방법을 보여주는 것입니다. 이를 통해 API에서 노출되는 데이터 모델을 이해한 사용자는 특별한 설명 없이도 쿼리 URL의 형식을 예측할 수 있게 됩니다.
우리는 앞서 쿼리 URL을 정의할 때 흔히 사용하는 패턴에 대해 설명했습니다, 그 패턴은 다음과 같습니다.
https://dogtracker.com/persons/{personId}/dogs
다음과 같은 동등한 형식보다는
https://dogtracker.com/search?type=Dog&owner={personId}
다음과 같은 이유들로 인해 첫 번째 스타일이 일반적으로 선호됩니다:
• API를 사용하는 많은 애플리케이션 개발자는 첫 번째 스타일이 더 읽기 쉽고 직관적이라고 생각합니다.
• API를 구현하는 개발자들은 일반적으로 첫 번째 스타일을 구현하는 것이 더 쉽다고 느낍니다.
• 많은 사람들에게 첫 번째 스타일의 쿼리 URL은 포괄적인 쿼리 기능에 대한 약속을 의미하지 않지만, 두 번째 스타일의 쿼리 URL은 모든 쿼리 매개변수와 값의 조합을 지원해야 한다고 해석될 가능성이 더 높습니다.
• 첫 번째 스타일의 쿼리 URL은 쿼리 매개변수 스타일로는 표현하기 어려운 그래프 탐색 쿼리를 쉽게 표현할 수 있습니다.
마지막 요점을 설명하는 예시는 다음과 같습니다:
https://dogtracker.com/dogs/123456/owner/spouse
이 쿼리 URL이 식별하는 리소스는 정규 URL이 https://dogtracker.com/person/98765일 수 있는 리소스입니다. 이 쿼리 URL은 그래프 쿼리를 인코딩하고 있는데, 먼저 ID가 123456인 개를 찾은 후, 소유자 관계(그래프 엣지)를 탐색하여 소유자로 이동하고, 이어서 배우자 관계를 탐색하는 방식입니다. 이러한 쿼리는 URL에서 경로 세그먼트의 순서로 표현하는 것이 매우 자연스럽지만, 쿼리 매개변수로 표현하는 것은 훨씬 더 어렵습니다.
이 섹션의 나머지는 이러한 스타일의 쿼리 URL 설계에 중점을 둡니다.
쿼리 URL에서 관계를 표현하기
리소스들은 거의 항상 다른 리소스와 관계를 맺고 있습니다. "링크 포함하기"라는 섹션에서 이러한 관계를 표현하는 방법을 다루었는데, 그렇다면 쿼리 URL에서는 이러한 관계를 어떻게 잘 표현할 수 있을까요?
우리의 dogtracker 예시 API를 다시 살펴봅시다. 기억하실지 모르겠지만, 우리는 /dogs라는 잘 알려진 URL과 /dogs/1234 같은 형태의 URL을 가지고 있었습니다.
특정 소유자에게 속한 모든 개들을 얻기 위해, 사용자는 다음과 같은 GET 요청을 할 수 있습니다:
/persons/5678/dogs
이 쿼리의 의미를 다음과 같이 정의할 수 있습니다:
• URL이 /인 리소스에서 시작합니다.
• /의 리소스에서 persons 관계를 탐색합니다.
• persons 관계의 여러 값 중에서 5678 리소스를 선택합니다.
• 선택한 사람의 dogs 관계를 탐색합니다.
이 쿼리 URL의 일반적인 패턴은 다음과 같습니다:
/{relationship-name}[/{resource-id}]/.../{relationship-name}[/{resource-id}]
여기서 대괄호 안의 {resource-id} 경로 세그먼트는 이전 경로 세그먼트의 관계가 다중 값인 경우에만 필요합니다. 이 패턴은 첫 번째 세그먼트조차도 관계 이름으로 볼 수 있다는 관찰에 의존합니다. 이는 URL이 /인 리소스의 persons 관계입니다.
URL과 표현에서 관계를 대칭적으로 표현하기
위의 쿼리 URL은 사람과 개 사이에 관계가 있음을 암시합니다. 이 관계를 리소스 표현에서도 명시적으로 만들어 주면 API를 배우고 이해하기가 더 쉬워집니다. 이는 "Include links"라는 섹션에서 설명한 대로 URL 값 리소스 속성을 제공하는 것입니다. 즉, 쿼리 URL 구조에서 개(dogs) 관계가 암시된다면, 모든 사람(person) 리소스에 dogs 속성을 포함하고, 반대로 각 사람 리소스에 관계를 나타내는 dogs 속성이 있다면 사람을 가져오지 않고도 탐색할 수 있는 쿼리 URL을 제공하는 것입니다. 우리의 쿼리 URL은 또한 /가 persons 관계와 dogs 관계를 가지고 있음을 암시합니다. 따라서 그 표현은 다음과 같이 보여야 합니다:
{
"self": "https://dogtracker.com/",
"kind": "DogTracker"
"persons": "https://dogtracker.com/persons",
"dogs": "https://dogtracker.com/dogs"
}
쿼리 URL에 대한 일반 모델
쿼리 URL에 대해 이러한 관계 탐색 해석을 채택하면, 인기 있는 웹 API에서 발견되는 쿼리 URL과 월드 와이드 웹의 단일 루트 링크 모델 간의 만족스러운 개념적 통합이 이루어집니다. 쿼리 URL은 중간 리소스를 가져올 필요 없이 데이터에 이미 정의된 관계의 시퀀스를 효율적으로 탐색할 수 있도록 허용합니다. 이는 쿼리 URL 패턴과 데이터 모델을 통합하므로 API를 배우기 쉽게 만듭니다. Rapier는 이러한 방식으로 쿼리 URL이 기본 데이터 모델과 일치하는 웹 API를 지정하는 것을 더 쉽게 만들기 위해 노력하는 오픈 소스 프로젝트입니다.
쿼리 URL 설계를 위한 이 패턴은 신성한 것은 아닙니다. 이 패턴의 가치는 API의 데이터 모델을 이해한 클라이언트 프로그래머에게 쿼리 URL을 예측 가능하게 만든다는 것입니다. 만약 당신이 더 선호하는 쿼리 API 패턴이 있다면 사용하세요. 하지만 일관된 패턴을 채택하는 것이 각 쿼리 URL을 개별적으로 설계하는 것보다 거의 확실히 더 나은 선택입니다. 만약 당신이 우리의 패턴보다 우수하다고 생각되는 패턴을 찾는다면, 그것을 공유해 주세요.
경로 매개변수 또는 행렬 매개변수
이전 섹션에서 논의된 쿼리 URL 스타일에 대한 대체 구문이 있습니다. /persons/5678/dogs 대신, 다음과 같은 URL을 사용할 수 있습니다:
/persons;5678/dogs
/persons;id=5678/dogs # synonym for the previous URL
/persons;name=JoeMCarraclough/dogs
다음과 같은 규칙으로 일반화합니다:
/{relationship-name}[;{selector}]/.../{relationship-name}[;{selector}]
세미콜론 뒤의 요소는 경로 매개변수 또는 행렬 매개변수라고 불립니다. 우리는 이 URL 스타일이 구문적으로 깔끔하고 개념적으로 명확하기 때문에 선호합니다. 각 경로 세그먼트는 정확히 하나의 관계 탐색을 나타냅니다. 경로 매개변수를 사용하지 않은 이전 예제에서는 경로 세그먼트가 다중 값 관계 이름이 포함된 경로 세그먼트인지에 따라 관계 이름이나 선택자를 포함하게 됩니다.
우리는 경로 매개변수에 대해 단점이 있다고 보지 않습니다. 다만 많은 사람들이 이를 잘 모르는 점이 있을 뿐입니다. Jax-RS는 경로 매개변수에 대한 명시적 지원을 제공하는 유일한 구현 프레임워크로 알고 있으며, Node.js의 Express와 같은 다른 프레임워크를 사용하여 이를 구현한 경험도 있습니다. Python이나 Ruby로 작성하는 경우 Sinatra, Bottle 또는 Flask도 잘 작동할 것이라고 생각합니다. 경로 매개변수를 사용할지 여부는 궁극적으로 개인의 선호도나 구현 고려에 따른 실용적인 결정일 수 있습니다.
컬렉션 필터링
위의 예제들은 관계 이름과 다중 관계 값 중에서 단일 리소스를 식별하는 선택자를 사용하여 쿼리 URL의 경로 부분을 구성하는 것이 얼마나 효과적인지를 보여줍니다. 하지만 많은 쿼리 API에는 관계 탐색 이상의 복잡한 부분이 있습니다.
복잡한 쿼리 절을 필터링하는 것은 전통적으로 URL의 쿼리 문자열 부분의 ? 이후에 배치하는 것이 일반적이었습니다. 예를 들어, 공원에서 뛰고 있는 모든 빨간 개를 얻으려면 다음과 같이 표현됩니다:
/dogs?color=red&state=running&location=park
경로와 쿼리 문자열 매개변수를 기반으로 한 쿼리 URL의 사용은 다음 예와 같이 결합할 수 있습니다:
/persons/5678/dogs?color=red
영구 자원이 아닌 응답은 어떻게 처리할까?
영구 자원의 표현이 아닌 응답을 반환하는 API 호출은 일반적입니다. 우리는 금융 서비스, 통신 및 자동차 분야에서 어느 정도 이를 보았습니다.
다음과 같은 단어들은 영구 자원응답을 처리하고 있지 않을 가능성을 나타냅니다:
• 계산하다 (Calculate)
• 번역하다 (Translate)
• 변환하다 (Convert)
예를 들어, 누군가가 내야 할 세금을 계산하는 것과 같은 간단한 알고리즘 계산을 하거나 자연어 번역(요청에서는 한 언어, 응답에서는 다른 언어)을 하거나 한 통화를 다른 통화로 변환한다고 가정해 보세요. 이 경우 데이터베이스에서 반환되는 영구 자원은 없습니다.
이러한 경우, 명사 기반의 문서 기반 모델에서 벗어날 필요는 없습니다. 핵심 통찰력은 URL이 응답에서 리소스를 식별해야 하며, 응답을 계산하는 처리 알고리즘은 식별하지 않아야 한다는 것입니다. 응답의 표현이 포함된 리소스는 사물이며, 웹의 명사 기반 엔터티 모델에 맞는 URL을 쉽게 발명할 수 있습니다. 클라이언트의 관점에서 결과가 계산되었는지 데이터베이스에서 검색되었는지는 중요하지 않으며, 실제로 계산된 리소스는 성능을 위해 이후에 지속적인 캐시에 저장되는 경우가 일반적입니다.
예를 들어, 100유로를 중국 위안으로 변환하는 API:
/monetary-amount?quantity=100&unit=EUR&in=CNY
또는 심플하게
/monetary-amout/100/EUR/CNY
응답의 리소스는 통화로 표현된 금액이며, 이는 명사 또는 명사구로 식별할 수 있는 사물입니다. 이 예제를 콘텐츠 협상을 사용하여 구현할 수도 있습니다:
요청:
GET /monetary-amount/100/EUR HTTP/1.1
Host: currency-converter.com
Accept-Currency: CNY
응답:
HTTP/1.1 200 OK
Content-Length: 9
683.92CNY
이 접근 방식에서 100유르는 금전적 수치(명사)이며, CNY는 내가 받고 싶은 통화 단위입니다. 이러한 상황에 대한 세 번째 해결책은 명사 기반 URL에 POST하는 것입니다. 예를 들어:
요청:
POST /currency-converter HTTP/1.1
Host: currency-converter.com
Content-Length: 69
{
"amount": 100,
"inputCurrency": "EUR",
"outputCurrency": "CNY"
}
응답:
HTTP/1.1 200 OK
Content-Length: 5
683.92
이 접근 방식은 많은 사람들에게 매우 자연스럽게 느껴지며, 만약 이 방법이 당신의 상황에서 최선의 해결책이라면 당연히 사용할 수 있습니다. 그러나 다음과 같은 두 가지 단점이 있다는 점을 고려해야 합니다:
• 표준 HTTP 메커니즘을 사용해 응답을 캐시할 수 없습니다. 응답을 캐시하기 위한 시스템을 직접 만들어야 하며, 이를 위해서는 응답에 대한 키(정체성)를 부여해야 합니다. 그 키에는 입력 금액, 입력 통화, 출력 통화가 포함될 가능성이 큽니다. 이러한 키는 이전 해결책에서 정의된 URL과 동등한 역할을 합니다. 만약 이러한 캐시 키를 정의하거나, 그럴 필요가 있다고 느낀다면, 아마도 다른 디자인을 선택했어야 했다는 신호일 수 있습니다.
• 여기서 논의된 POST 패턴을 사용할 때, 균일한 인터페이스 모델에서 벗어나기 시작할 수 있습니다. POST하는 각 엔터티는 일반적으로 고유한 입력과 출력을 가지며, 이는 전체 작업 집합을 뒷받침하는 더 넓은 데이터 모델에서 추론할 수 없습니다. 이는 섹션 "데이터 지향 접근 방식이 왜 유용한가?"에서 설명된 RPC에서 볼 수 있는 더 넓고 복잡한 인터페이스와 비슷해 보일 수 있습니다. 만약 이렇게 된다면, REST의 가장 기본적인 이점 중 하나를 잃어버리기 시작하는 것입니다. DBMS 비유를 확장하자면, 이것은 데이터베이스 스키마에 저장 프로시저를 도입하는 것과 유사합니다. 강력할 수 있지만 인터페이스가 더 복잡해지고 클라이언트와 서버 간의 결합도가 높아집니다.
이 방법이 문제를 해결하는 최선의 방법이라면 마음껏 POST를 사용해도 좋지만, 간단하게 GET을 사용할 수 있는 방법이 있다면 POST를 사용하지 않는 것이 좋습니다. 특히 GET이 API의 다른 작업들을 뒷받침하는 데이터 모델에서 자원을 다룰 수 있도록 설계할 수 있다면 더욱 그렇습니다.
결론적으로, URL이 동사를 식별하는 것처럼 보일 이유는 없습니다. URL은 항상 명사로서 사물을 식별할 수 있습니다.
[출처]