Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Stackoverflow, Twitter, Github, Medium의 URL 패턴 간단 정리

웹사이트나 API를 만들려면 URL 정의가 필요한데 데이터베이스 모델링이 만만치 않은 것처럼 URL을 정의하기도 쉽지 않고 API를 정의할 때랑 웹사이트의 URL을 정의할 때가 또 다르다. 웹사이트를 만들 때마다 URL을 어떻게 정의할지 고민하게 되는데 최근에 작업하려고 내가 좋아하는 사이트들이 URL을 어떻게 정의하고 있는지 살펴봤다.

stackoverflow

stackoverflow는 상당히 전통적인 개념의 REST에서 보통 말하는 교과서적인 URL을 그대로 사용하고 있었다.(URL 정의만 찾아본 것이므로 GET/POST 외에 다른 HTTP 메서드도 사용하는지는 확인하지 않았다.) 여기서 교과서적인 RESTful URL이란 의미는 /teams/:teamName/players/:playerName같은 식으로 리소스를 표현하기 위해서 컬렉션과 특정 리소스를 지정하는 방식을 의미한다.

  • /users/ : 전체 사용자를 보여준다.
  • /users/:userSeq/:userName : 특정 사용자의 정보를 보여준다. 그래서 내 URL은 /users/518864/outsider가 된다. 여기서 :userSeq만 사용하지 않고 :userName까지 붙인 이유는 URL의 가독성 때문으로 보인다. :userSeq같은 경우는 바뀌지 않지만 :userName은 바꿀 수 있으므로 저런 계층 구조로 만든 것이다. 그래서 /users/:userSeq/에 접속하면 /users/:userSeq/:userName로 리다이렉트가 되고 /users/:userSeq/뒤에 아무 문자나 붙여도 해당 사용자로 리다이렉트가 된다.
  • /questions : 전체 질문 목록을 보여준다.
  • /questions/:questionId/:questionTitle : 특정 질문을 보여준다. 예시로 /questions/27815644/rotate-rectangular-element-90-degrees-on-a-grid와 같은 URL이고 질문의 제목을 :questionTitle로 붙여주어서 URL을 읽을 수 있게 만들어 주었고 마찬가지로 /questions/:questionId/로 접근하면 전체 URL로 리ㄷ이렉트 해준다.
  • /tags : 태그 목록을 보여준다.
  • /questions/tagged/:tagName : 특정 태그가 달린 질문 목록을 보여준다.
  • /unanswered : 대답이 안 달린 질문을 보여주는 URL이다.
  • /unanswered/tagged/:tagName : 대답이 안 달린 질문 중 특정 태그가 달린 질문을 보여준다.
  • /users/signup : 회원 가입 페이지
  • /users/login : 로그인 페이지
  • /legal/privacy-policy : 이용약관 같은 경우는 /legal 등의 밑으로 붙지만 스택오버플로우 특성상 부모사이트인 http://stackexchange.com로 이동하므로 URL이 충돌하거나 하진 않는다.

Twitter

  • /:userId : 해당 사용자의 페이지를 보여준다. 루트 경로 아래에 바로 사용자 아이디를 사용하고 있는데 이는 사실상 와일드카드를 사용하고 있는 것과 마찬가지다. 이용약관, 회원 가입 등의 URL과 충돌하므로 내부적으로 라우팅의 우선순위를 주고 해당 URL의 예약어는 userId로 가입을 못 하게 막는다던가 하는 처리를 한 것으로 보인다.
  • /:userId/status/:tweetSeq : 사용자의 특정 트윗을 보여준다.
  • /:userId/following : 팔로잉 목록을 보여준다.
  • /tos : 이용약관 페이지
  • /signup : 회원가입
  • /login/ : 로그인

Github

  • /:userId : Github도 Twitter와 같게 루트 경로 아래 사용자 아이디를 사용해서 와일드카드를 쓰고 있다. 그룹의 경우 /:organizationId로도 쓸 수 있지만 Github이 그룹과 사용자를 구분하지 않으므로 같은 패턴을 쓰고 있다. /login, /join 등이 있으므로 Twitter와 같게 라우팅 충돌을 막고 예약어의 회원 가입을 막는 등의 처리를 하고 있다. 실제로 회원 가입을 할 때 보면 사용 중인 아이디는 Username is already taken라고 나오지만 login등은 Username is a reserved word라고 표시된다.
  • /:userId/:repository : 저장소 페이지. 저장소가 사용자에 포함되어 있으므로 사용자 밑으로 저장소가 들어가고 저장소와 관련된 다른 메뉴들은 이 하위로 들어간다.
  • /:userId/:repository/issues : 저장소의 이슈 목록
  • /:userId/:repository/issues/:issueNo : 저장소의 특정 이슈
  • /:userId/:repository/pulls : 저장소의 풀리퀘스트 목록
  • /:userId/:repository/pull/:pullRequestNo : 저장소의 특정 풀리퀘스트(목록은 pulls인데 특정 PR을 볼 때는 pull이네. 이슈에서는 둘 다 issues이면서..)
  • /login : 로그인
  • /join : 회원가입 페이지
  • /site/terms : 이용약관 페이지. 그외에도 /security/contact등도 있지만 실제로는 https://help.github.com로 모두 리다이렉트 된다.(왜 바로 help.github.com으로 안 보내는지는 궁금하지만...)
  • /explore : 추천 프로젝트 보기
  • /search : 검색 페이지
  • /showcases : 쇼케이스 페이지

Medium

  • /@:userId : 사용자 페이지. Medium은 특이하게 사용자 아이디 앞에 @를 붙이는데 이는 루트 경로 밑에 와일드카드 사용으로 인한 예약어와의 충돌을 막기 위한 거라고 생각한다. 처음 URL에서 @를 보았을 때는 이상하게 느껴졌는데 @가 멘션을 의미한다는 일반적인 관례가 있으므로 예약어 충돌을 막을 수 있는 괜찮은 접근이라는 생각도 든다.
  • /@:userId/:postTitle-postId : 특정 글의 페이지. 약간 특이한 방식인데 예시로 /@ev/a-mile-wide-an-inch-deep-48f36e48d4cb같은 URL이 된다. 제목 뒤에 글의 아이디로 보이는 값이 붙어있는데 추측 상으로는 같은 제목의 글을 작성했을 때 충돌을 막기 위함이라고 생각한다. 그래서 뒤에 글의 아이디가 붙어있더라도 제목부분에 다른 글자를 입력하면 404페이지가 나온다.
  • /me : 로그인했을 때의 사용자 페이지로 자신의 /@:userId로 리다이렉트가 된다. 로그인하지 않았을 경우에는 로그인 페이지로 리다이렉트 된다.
  • /me/stats : 로그인한 사용자의 통계 페이지. /@:userId를 사용하지 않고 /me를 별도로 만든 이유는 잘 모르겠다.
  • /me/stories/drafts : 로그인한 사용자의 작성 중인 글 목록.
  • /me/stories/public : 로그인한 사용자의 발행한 글 목록.
  • /p/new-post : 새 글 작성 페이지.
  • /p/import : 다른 사이트에서 글 가져오는 페이지. 글 작성과 관련된 부분은 /p 밑으로 붙는데 post를 의미하는 것 같다. 그럼에도 stories는 /me 밑에 있어서 일관성은 떨어진다고 생각한다.
  • /p/:postId/edit : 글의 수정 페이지. 글의 URL은 앞에서 본대로 /@:userId/:postTitle-postId이지만 여기서 수정을 누르면 /p/:postId/edit로 이동한다.(역시 일관성은 별로라는 생각이....)
  • /search : 검색 페이지
  • /collections : 사용자가 구독 중인 컬렉션(글을 특정 그룹으로 모아놓은) 목록 페이지. 로그인 하지 않았다면 컬렉션 검색 페이지로 이동한다.
  • /:collectionName: 사용자나 에디터가 만든 특정 컬렉션의 페이지로 /backchannel같은 URL이 된다.(사용자의 경우 @로 와일드카드 충돌을 막았지만, 컬렉션 같은 경우는 루트 경로 밑에 와일드카드를 바로 사용했다. 이것 때문에 @를 사용했을 수도 있고...)
  • /m/signin : 로그인 및 회원 가입 페이지. /m/은 무슨 의미 인지 왜 붙는지 잘 모르겠다.
  • /policy/medium-privacy-policy-f03bf92035c9 : 이용약관 페이지. 사용자의 글과 거의 비슷한 형태이지만 policy앞에 @가 붙지 않는다.

내 생각

상당히 대표적인 사이트임에도 URL 정리가 만만치 않구나 하는 생각이 든다. 개인적으로는 Github이 그래도 가장 깔끔한 URL이 아닌가 싶고 Medium은 너무 많이 고민하다가 오히려 꼬인듯한 생각이 든다. 나중에 기능이 추가되면서 레거시때문에 어쩔 수 없이 취한 선택도 있을 수 있고 서비스마다 특성이 다르기에 URL 정의에 정답은 없다고 생각한다.

다만 평소에 /* 식으로 첫 경로에 바로 와일드카드가 오는 것은 지옥의 문을 여는 것과 같다고 생각하고 있었는데 뜻밖에 꽤 많은 서비스가 이렇게 사용하고 있다는데 놀랐다.(심지어 깃헙은 저장소 주소는 /*/*이다.) 내가 보통 하는 접근은 Stackoverflow 식의 <컬렉션>/<특정아이템>/<컬렉션>/<특정아이템>같은 계층 구조이지만 URL이 상당히 장황해지는 단점이 있고 단축 URL 같은 서비스가 있지만(난 별로 안 좋아하지만) 서비스 자체가 간결한 URL 구조를 가진다면 좋을 것이다. 그냥 생각해도 /users/jquery/repository/jquery 보다는 /jquery/jquery가 간결해지고 보기 좋다. 물론 전자는 의미가 명확하다는 장점이 있기는 하지만 서비스를 쓰다 보면 아이디란 걸 누구나 알 수 있으므로(github이나 twitter 등) 큰 차이 있나 싶기도 하다.

Stackoverflow나 Medium 같은 경우는 사용자나 글의 시퀀스번호를 URL에 넣어서 이용하고 있다. 과거에는 시퀀스번호로만 URL을 구성했는데(게시판의 경우 /board/2처럼) URL 가독성이 중요해 지면서 타이틀이나 유저명을 혼합해서 표시하는 방식을 취하는 것으로 보인다. 물론 시퀀스번호를 아예 안 쓰는 곳도 많지만 시퀀스를 혼합해서 사용하는 방식은 약간의 절충형이라고 보인다. 어차피 의미 없는 시퀀스는 사람들이 무시하면 그만이니까..(괜찮은 방법 같기도 하고 아닌 것 같기도 하고...)1


  1. 물론 우리는 한글을 사용하므로 URL에 가독성을 주기 위해 한글이 붙이는 방식이 좋은가 아니냐도 또 하나의 관심거리이긴 하다. 

2015/01/11 19:51 2015/01/11 19:51