스펙만 가지고 웹 서비스와 웹 API를 만들 때, URI를 어떻게 설계해야 좋을지 파악하기 쉽지 않다. 좋은 URI란 무엇인지, 좋은 URI를 설계하기 위한 방법을 알아보자 !
URI의 좋고 나쁨, 어떤 URI는 좋은것이고, 어떤 URI는 나쁜것일까 ?
좋은 URI를 Cool URI라고 부르는데, 버너스리는 "Cool URI Don't Change"라고 하였다. 이 당시 웹페이지의 URI가 변경되는 것은 일상다반사였다.
이것은 웹의 근간을 뒤흔드는 문제였고, 버너스리는 이런 상황을 우려해 URI는 변하지 않아야 한다. 변하지 않는 URI야말로 최고의 URI다 라는 주장을 Cool URI라는 단어에 담았던 것이다.
말은 그렇게 하지만 결국 URI는 변하는 경우가 많았다. 웹 서비스의 설계로 회사가 망하는 것을 막진 못하지만, 좀처럼 변하지 않는 URI로 웹 서비스를 구축할 수는 있다. 변하지 않음을 키워드로 삼아 URI의 설계에 대해 생각해나가보자
URI가 잘 변경되지 않기 위해 프로그래밍언어에 의존적인 부분을 배제해야한다. 예전엔 확장자를 사용하여 구현에 의존적인 부분이 있는 URI들이 있었다.
이런 URI들이 사라지게 된 첫 번째 이유는 CGI(Common Gateway Interface)의 쇠퇴이다. 요청할 때마다 프로세스를 실행하는 CGI 방식은 성능면에서 문제점이 있었기 때문에 다른 방법으로 대체 되어갔다.
자바의 예를 들어보자
http://example.com/sevlet/LoginServlet
확장자를 사용하지는 않지만, 문제가 되는 구조는 앞의 예와 완전히 동일하다. servlet은 특정 서블릿 컨테이너의 기본 경로이고 시스템을 서블릿에서 PHP로 바꾸는 순간 변경된다. 또한 L과 S가 대문자인 점도 Java의 문화지만 Perl이나 Ruby는 소문자로 하는 문화이다. 이처럼 어떤 특정 언어에 의존하는 문자열을 URI에 포함시키면 그 언어를 변경하자마자 URI를 사용할 수 없게 된다.
앞의 예는 주로 파일명에서 오는 구현 의존이었지만 이보다 더 심각한 구현 의존도 있다. 바로 특정 프레임워크의 특정 확장자나, 메서드명이 URI에 들어가는 것이다.
똑같은 프레임워크를 사용하더라도 리팩토링을 통해 메서드명이 변경되면 바로 URI가 바뀐다.
세션을 포함하는 것은 말할 것도 없다. 세션 ID는 로그인 할 때마다 바뀌므로 이 URI는 시스템에서 다시 로그인할 시 변경된다.
URI는 리소스의 이름이다. 즉 명사여야한다. show라는 메서드 명이 들어가게 되면, 명사와 동사의 관계 같은 URI와 HTTP의 관계가 깨지게 된다. 따라서 URI는 전체적으로 명사가 되도록 설계해야한다.
위의 내용들을 지켜 URI를 설계해보면
http://example.com/login
심플한 URI가 되었다. Cool ~~!
URI가 심플해지면 뭐가 좋을까 ? 사용성이 향상된다는 점이다.
글자수가 적어지면 기억하는 것도 간단해진다. 구현 의존적인 문자열은 일반인들에게 친숙하지 않다. 기억하기 쉽고, 개발자가 아닌 일반인들도 사용하기 쉽다는 것이 Cool URI의 좋은 점이다.
그렇다면 URI를 변경하고 싶을 땐 어떻게 할까 ? 사실 현재 운용중인 시스템의 URI를 안이하게 변경해선 안된다.
하지만, 언제까지 안바꿀 수는 없다. 시스템 전체의 기능 변경, 추가 등의 시스템을 교체해야만 하는 이슈가 생긴다면 ?
어떻게든 URI를 변경해야할 때는 가능한 Redirect 하도록 하자. Redirect란 오래된 URI를 새로운 URI로 전송하는 HTTP의 기능을 말한다.
본 절에서는 URI를 설계할 때 사용할 수 있는 테크닉으로 확장자로 표현을 지정하는 방법인 매트릭스 URI를 소개한다.
지금까지 확장자는 URI 설계에 있어 좋지 않은 것으로 말했다. 하지만 이것은 '.java', '.class'같은 구현에 의존하는 확장자이다. 리소스의 표현을 지정하는 확장자 사용법에 대해서 알아보자
예를 들자면 세계를 상대로 활동하는 기업에서는 복수의 언어로 콘텐츠를 나타내는 것이 일반적이다. 여기서 같은 내용의 콘텐츠가 한국어와 영어로 기술되어 있다고 해보자.
이 케이스는 콘텐츠는 하나이고, 그 표현이 영어거나 한국어라는 것으로 가정한다. 2010년 5월 1일 발표한 콘텐츠의 경우 URI는 다음과 같다
http://example.com/2010/05/01/press
HTTP에는 콘텐트 네고시에이션이라는 편리한 기능이 있다. 한국판 OS를 사용하는 유저에겐 한국어를 영어판 OS를 사용하는 유저에게는 영어를 반환한다. 예를 들어, 한국어 OS사용자로부터는 다음과 같은 요청이 들어온다.
GET /2020/05/01/press/HTTP/1.1
Host: example.com
Accept-Language:ko, en_us;q=07,en;q=0.3
Accept-Language 헤더에 클라이언트가 희망하는 언어를 지정한다. 이 예에서는 한국어 미국 영어, 그 밖의 영어 순으로 우선순위가 매겨져 있다.
이처럼 콘텐트 네고시에이션에 의해 클라이언트의 언어설정에 따라 자동으로 적합한 표현을 반환할 수 있다. 마찬가지로 Accept 헤더를 사용하여 미디어 타입을 지정하거나 문자 인코딩도 지정할 수 있다.
좀 더 간편하게 접근할 수 있도록 하기 위해 릴리스 언어를 명시적으로 지정한 다음과 같은 URI를 사용하면 된다.
http://example.com/2010/05/01/press.ko
하나의 리소스가 복수의 표현을 가질 때 각 리소스의 표현을 나타내는 URI에 '.ko' 같은 확장자를 사용하는 것은 나쁜일이 아니다. 표현의 종류로는 언어뿐 아니라 형식도 포함된다.
예를 들어 하나의 리소스를 HTML, JSON, txt로 표현할 수 있을 때엔 JSON, html, .txt 같은 확장자를 붙여 각각의 표현으로 나누는 것이 좋다.
이 요청에 따라 콘텐츠를 돌려주면 되는 것이다.
URI는 슬래시를 사용하여 계층을 표현할 수 있다.
http://example.com/diary/2020/05/01
이는 날짜 정보가 년 -> 월 -> 일 이라는 계층구조를 가지고 있기 때문이다. 하지만 다차원을 가지는 정보 예를 들어 지도 같은 것은 계층으로 표현할 수 없다. 그럼 어떻게 URI로 표현할까 ?
예를 들자면 위도와 경도 외에 표시축적이나 지도인지 항공사진인지 나타내는 플래그 등 복수의 파라미터가 필요하게 된다. 이 파라미터들은 각각 독립적인 축을 가지기 때문에 각각의 리소스를 계층구조로 표현할 수 없다.
여러 파라미터의 조합으로 표현하는 리소스는 매트릭스 URI를 사용한다. 계층 구조를 표현하는 대신 세미콜론(;)으로 구분해 리소스를 표현한다.
http://example.com/map/lat=35.7054234;lng=123.25245346
세미콜른(;)은 순서의 의미를 가지지 않을 때 사용하고 순서의 의미를 가져야 한다면 ,로 구분한다.
심플한 URI는 가독성이 높기 때문에 사용자가 URI의 구조를 추측하기 쉬워진다. 하지만 URI를 추측해서 접근하여도 반드시 리소스가 있으란 법은 없다.
웹에서의 리소스 조작은 HTML 같은 리소스 내의 링크를 따라가며 이루어진다. 즉, 클라이언트는 어디까지나 서버가 제공하는 URI를 그대로 사용할 뿐이다. URI의 내부구조를 상상해 조작하거나, 클라이언트 쪽에서 URI를 구축해서는 안된다. 그 이유는 서버 쪽에서 URI의 구조를 변경하는 순간 시스템이 동작하지 않게 되는, 밀결합(tight coupling) 상태가 되기 때문이다.
이렇게 URI를 클라이언트 쪽에서 구성하거나 확장자로 리소스의 내용을 추단하거나 할 수 없는 특성을 'URI는 클라이언트에 있어 불투명(Opaque)하다'라고 한다.
URI는 자칫하면 웹 애플리케이션 프레임워크가 은폐하기도 하고, 일반 프로그래머는 그다지 신경 쓰지 않아도 그만인 존재가 되기 쉽다. 하지만, URI는 다음과 같은 점에서 아주 중요하다
URI는 리소스의 이름이다.
URI는 수명이 길다
URI는 브라우저가 어드레스 란에 표시한다.
이런 관점에서 URI는 웹 서비스와 웹 API의 설계에 있어 가장 중요시해야 할 부분이라고 할 수 있다.