(1) 2008년을 즐겁게 만들어줄 새해 선물, 레일스 2.0
February 25th, 2008
07-08 시즌, 루비팀 전망 보고서는 마이크로소프트웨어 2008년 1월호 특집 <웹 개발 기대주 BEST 5>에 기고한 글을 마소의 허락을 받고 블로그에 올리는 것입니다.
레일스 1.0을 발표하며 들뜬 목소리로 '이제 우리는 견고해졌어요.'라고 외치던 게 바로 2년전이다. 지금은 그 시기와는 분위기가 무척 다르다. 누가 이런 성공을 상상이나 했을까? 단순히 재미있는 눈요깃감에서 전 세계적으로 100여권이 넘는 책이 만들어지고, 몇 천 명 규모의 컨퍼런스도 금방 매진이 되고, 기업시장까지 노리는 소위 거물이 되었다. 혹자는 웹 프레임워크를 레일스가 나오기 전과 후로 구분할 수 있다고까지 말한다. 왜 그렇게 많은 사람들이 레일스에 열광할까? 뭐가 그렇게 특별하기에? 혹시 웹 2.0처럼 이 시대 IT 업계에서 흘러 다니는 허울뿐인 유행어는 아닐까? 어떤 면에서는 이런 비판이 맞을지도 모르겠다. 하지만 분명하게 말할 수 있는 사실 하나는 레일스에 배울 점이 많다는 사실과 알게 모르게 요즘 나오는 프레임워크들에 영향을 끼쳤다는 점이다. 그리고 '아프지 않은 웹 개발, 프로그래머를 행복하게'라는 모토를 잘 실천하고 있다.
레일스는 자기주장이 강한 소프트웨어(opinionated software)다. '이 세상은 모두 CRUD로 만들 수 있어'라든지, '데이터베이스 스키마도 버전을 효율적으로 관리할 수 있어'라든지 이런 주장을 자주 한다. 그런데 신기한 것은 이런 것들이 처음 들으면 뚱딴지같은 소리로 들리지만, 가만 생각해보면 일리가 있고 또, 현재 가진 문제점을 해결하는데도 도움이 된다는 점이다. 필자가 레일스를 좋아하는 이유다. 레일스 버전이 올라갈 때마다 이런 발상의 전환을 한두 가지씩 발견할 수 있었다. 1.0에서 보여준 스캐폴드(scaffold) 코드 자동 생성이 그랬고, 1.1의 RJS(루비로 자바스크립트를 메타프로그래밍하는 방식), 또 1.2에서는 리소스(Resources) 개념이 놀라움의 대상이었다. 그렇다면 2.0은? 하나로 꼬집어 말할 수 없는 뭔가가 있다. 큰 기능 추가는 없지만 하나하나 뜯어보면 바뀌지 않은 곳이 없을 정도로 많은 변화가 있었다. 이런 것을 하나씩 알아가고, 또 그 결과로 코드가 깔끔해지는 것이 요즘 필자가 느끼는 행복이다. 지금부터 2008년을 즐겁게 만들어줄 새해 선물 레일스 2.0의 특징을 함께 하나씩 살펴보자.
레일스의 철학은 실용주의
기능을 하나씩 보기에 앞서 그 아래에 깔려있는 레일스의 철학을 이야기하면 좋겠다. 크게 두 가지인데, 그 중 첫 번째는 레일스가 만든 유행어인 설정보다는 관례(Convention Over Configuration)다. 말 그대로 많은 설정을 주기보다는 80% 경우에 먹힐 수 있는 관례를 줘서 대부분의 프로그래머가 쉽게 이용할 수 있도록 하자는 것이다. 이는 레일스가 주장하는 생산성의 근원이기도 하다. 그래서 레일스 애플리케이션은 모두 같은 디렉터리 구조를 갖고 있고, URL 하나만 보면 어떤 컨트롤러의 어떤 메서드가 호출되고, 또 어떤 테이블의 데이터를 읽어서 어떤 템플릿을 그릴지를 모두 유추할 수 있다. 프로그래머는 그런 작은 문제들에 신경을 쓰지 않아도 되고 또 동료에게 구구절절 설명할 필요도 없고, 문서도 만들지 않아도 된다. 물론 코딩양도 줄어들고 자동화되는 부분이 늘어난다. 생산성이 높아지는 이유다. 관례를 벗어나면 고행길이라고는 하지만, 이 길도 여러 명이 지나면 오솔길이 되고 또 대로가 생기기도 한다. 루비라는 유연한 언어가 주는 장점으로 어떤 프레임워크보다 간편하게 레일스 코드 일부를 재정의할 수 있고, 또 플러그인으로 만들어 공유할 수도 있기 때문이다. 웬만한 플러그인은 다 있다고 해도 과언이 아니므로 직접 구현하기 전에 검색은 필수라 하겠다.
경험이 쌓이면서 레일스의 관례는 더 강해졌다. 이제 설계의 일부까지 관례가 되었다. 특정 리소스를 정의하면 그 리소스를 사용하는 방법(기본 메서드 7가지)을 알 수 있고, URL을 알 수 있고, XML, JSON API를 사용할 수 있다. REST가 관례가 된 것이다. 팀 내에서 이에 대한 공감대만 형성된다면 생산성은 더 높아질 것이다. 잠시 후에 더 자세히 다루도록 하자. 그리고 호불호가 더 명확해졌다. SOAP은 싫고, REST는 좋다. AJAX, OpenID, ATOM은 계속 레일스와 친구로 남을 것이다.
레일스의 두 번째 철학은 반복을 하지 말자는 DRY(Don't Repeat Yourself)다. 이는 루비 커뮤니티 전반에 깔려있는 생각이기도 하다. 하지만 아주 가끔 너무 과하다는 생각이 들기도 한다(예를 들면, CSS가 중첩되면서 나오는 코드 중복을 없애기 위해 CSS를 메타프로그래밍하는 루비 라이브러리를 만들어서 이제 DRY해졌다고 주장할 때). 아무튼 DRY에 대해 집착하면 할수록 그에 비례해서 애플리케이션 코드는 깔끔해진다. 예를 들면 이런 거다. 특정 리소스를 만들고 난 다음 그 리소스로 리다이렉트하는 코드를 이전에는 redirect_to resource_path(@resource) 라고 썼다. 여기 resource가 두 번 반복된다. 한번으로 줄이면 redirect_to(@resource)가 된다. 또, render :xml => @resource.to_xml 이라고 쓰던 것을 render :xml => @resource라고 쓴다. to_xml이라는 중복이 제거되었다. 이런 식으로 DRY해지려는 개선 노력이 레일스 2.0 전반에서 일어났다. 프로그래머 입장에서는 깔끔해지기도 하고, 또 URI를 스스로 알고 있는 리소스 개념이 살아나기도 해서 좋기만 하다.
이 두 가지 철학을 한 단어로 표현하면 바로 실용주의다. 그럼 레일스 2.0이 얼마나 실용적으로 변했는지 살펴보자.
그림 1. 2월 6일 레일스 2.0이 공개되었다.
레일스, 웹과 친해지다
경험이 쌓이면서 레일스가 웹 생태계를 더 잘 이해하게 되었다. 그래서 HTTP 상태코드도 유창하게 구사하고, <리스트 1>처럼 HTTP Authentication도 잘 지원한다(물론, 이전에도 플러그인으로 사용할 수 있었다). 참고로, <리스트 1>의 코드를 테스트하려면 ActionController::HttpAuthentication::Basic.encode_credentials 메서드를 사용해 @request.env['HTTP_AUTHORIZATION']을 채워주면 된다.
- class AdminController < ApplicationController
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic do |name, pass|
User.admin?(name, pass)
end
end
end
리스트 1. HTTP Authentication으로 관리자 인증을 수행한다.
그리고 UI가 풍부한 애플리케이션에서 어쩔 수 없이 늘어나는 자바스크립트와 CSS를 하나의 파일로 합쳐서(Asset caching) 전송해 HTTP 성능을 향상시킨다. 정적파일 전송에 사용하는 서버도 여러 대로 지정해 동시 접속자 수를 늘려 로딩을 빠르게 하는 트릭도 추가되었다. 이 기능을 이용하려면 <리스트 2>처럼 선언하면 자바스크립트나 CSS를 읽어올 때 asset0.site.com ~ asset3.site.com까지 4개의 호스트이름을 골고루 사용한다. 벤치마크 결과 4대로 요청을 분리할 때 가장 좋은 성능이 나왔다고 한다.
- # config/initializers 폴더에 다음 설정을 넣는다.
config.action_controller.asset_host = "http://asset%d.site.com" - # View에서는 자바스크립트 파일을 하나로 합쳐서 전송한다.
javascript_include_tag 'app', 'logic', :cache => true
리스트 2. HTTP 성능 향상을 위한 노력들
이 외에도 레일스에서 기본값으로 사용하는 세션 저장소가 쿠키로 바뀐 점도 눈에 띈다. 정말 엄격한 보안이 필요한 애플리케이션이 아니라면 암호화된 쿠키값 정도로 세션을 이용해도 좋겠다. 물론 이 경우, 서버 측에서 세션을 저장하거나 읽어오는 오버헤드가 사라지므로 실용적이다. 하지만 4K 제한이 있고, 또 클라이언트 사이드이기도하니 민감한 내용은 세션에 담지 않는 것이 좋겠다.
우리의 미래는 맑고 푸른 리소스 세상
앞에서 말했듯 레일스 애플리케이션은 기본적으로 REST 스타일을 따르는 것이 좋다. 관례이기 때문이다. 이제는 스캐폴드 생성기를 이용해 만들어진 코드들도 XML API를 얻었다. 이렇게 공짜로 얻어지는 세련되고 사용하기 편한 API를 논외로 치더라도 REST 방식은 유용하다. 개발 중인 애플리케이션에서 제공하는 리소스에 대한 일관된 접근법이 생기므로 협업하기도 편하다. 그리고 URL이나 메서드명을 고민하는 대신 애플리케이션에서 제공하는 리소스가 무엇인지 고민하는 것은 전체적인 설계를 깔끔하게 만드는데 도움이 된다. 필자는 한걸음 더 나아가 라우팅 파일(routes.rb)에서 기본 라우팅 즉, ':controller/:action/:id'에 대한 매핑을 지워버리면 어떨까 하는 제안을 한다. 37signals에서 가장 최근에 내놓은 Highrise에서는 2개의 컨트롤러를 제외하고는 모두 REST를 따랐다고 한다. 스프링노트는 초기에 만든 컨트롤러들이 REST가 아닌 방식(레거시 컨트롤러라고 과격하게 말하기도 한다)으로 되어있는데, 하나씩 옮겨가는 중이다. 도대체 REST가 뭐길래라고 생각하는 독자가 있다면 RESTful Web services라는 책을 한번 읽어보면 좋겠다. 개념탑재에는 그만이다.
리소스 세상에 발맞추기 레일스 2.0의 라우팅도 많이 변했다. 라우팅의 역할은 예쁘고 보기 편한 URL을 만드는 것도 있지만 이제는 그보다 중요하게 리소스의 관계를 표현하는 책임이 부여되었다. <리스트3>을 보면 라우팅에 has_many, has_one 등의 문법(액티브레코드에서 모델간의 관계를 나타내는데 사용했던)이 도입되었음을 볼 수 있다. 모델간의 관계 중 외부로 노출하고 싶은 것은 라우팅에 적어주면 된다. 이렇게 하면 URL은 posts/:id/comments/:cid 처럼 생기고 post_comment_id(@comment)등의 도우미 메서드도 함께 정의된다. 그리고 네임스페이스도 새롭게 추가되었다. 네임스페이스가 가장 잘 활용될만한 부분은 역시나 관리자 화면이 아닐까 싶다.
- map.resources :posts,
:has_many => [:comments, :tags],
:has_one => :user
map.namespace(:admin) do |admin|
admin.resources :posts
end
리스트 3. 라우팅에 새로 추가된 메서드들
드디어 정식 가족이 된 액티브 리소스
레일스는 SOAP 대신 REST를 택했다고 했다. 그 결과 액션 웹 서비스 프레임워크가 레일스에서 빠지고 액티브 리소스(Active Resource)가 새 식구로 들어왔다. 이제 첫 버전이라 좀 부족한 모습을 보이기도 하지만, 그 가능성만은 무시하지 못할 것 같다. 액티브 리소스를 한마디로 표현하면 REST 클라이언트 라이브러리다. 그런데 액티브 리소스는 외부 웹서비스의 리소스를 마치 데이터베이스 테이블을 ORM을 통해 접근하는 것과 같은 느낌으로 쉽게 다룰 수 있게 해준다. 액티브레코드가 제공하는 메서드의 일부를 그대로 사용할 수 있다. 그래서 필자는 Object Resource Mapping(ORM)이라고 단어로 설명하기도 한다. 레일스 REST 방식의 API를 제공하는 Highrise나 스프링노트같은 서비스는 액티브 리소스를 이용해 간편하게 접근할 수 있다. <리스트 4>는 액티브 리소스를 이용해 스프링노트에 페이지를 만들고, 수정하고 삭제하는 코드다(참고- SpringnoteResources - 스프링노트 루비 라이브러리). 코드만 봐서는 액티브 레코드인지 액티브 리소스인지 분간도 힘들 정도다.
- # 페이지 만들기
# POST https://api.springnote.com/pages.xml
page = Springnote::Page.create :title => 'NewName', :source => 'NewContents'
# 페이지 수정하기
# PUT https://api.springnote.com/pages/144.xml
page = Springnote::Page.find(144)
page.source = '<p>New Contents</p>'
page.save
# 페이지 지우기
# DELETE https://api.springnote.com/pages/144.xml
Springnote::Page.find(144).destroy
리스트 4. 액티브 리소스를 이용해 스프링노트의 페이지 리소스를 다룬다
그림 2 간단한 액티브 리소스 사용법 (출처:http://www.undefinedrange.com/articles/2007/05/16/active-resource-cheatsheet)
섹시해진 액티브 레코드
많은 사람들이 레일스의 가장 큰 장점으로 꼽고 또 간결하고 보기 좋은 코드를 만들어주는 것으로 알려진 액티브 레코드는 더 섹시해졌다. 한가지 예로 마이그레이션 코드를 보자. <리스트 5>는 posts 테이블을 만드는 마이그레이션 코드다. 이전에는 t.column :body, :text라고 쓰던 부분을 이제는 t.text :body라고만 쓰면 된다. 불필요한 코드가 사라져서 훨씬 보기 편해졌다.
또 하나 테스트에서 사용할 용도의 픽스처를 정의하는 새로운 방법도 도입되었다. 사실 프로젝트가 커지면 커질수록 픽스처는 점점 관리하기 힘들어지는 애물단지가 되어간다. 테이블간의 관례를 맞추기 위해 하나하나 id를 적고 또 관리해야만 하기 때문이다. 다행히 Foxy Fixtures라는 이름의 플러그인이 레일스의 일부가 되면서 이런 수고를 조금은 덜 수 있게 되었다. 이제는 관계를 지정할 때 id보다는 실제 값을 사용한다. 그렇게 되면 훨씬 읽기 쉬운 픽스처를 만들 수 있다. id을 따로 관리해야하는 수고도 없어졌다. 다대다 관계, 다형적 관계도 잘 표현할 수 있으므로 픽스처를 자주 사용한다면 참고하기 바란다.
- create_table :posts do |t|
t.integer :user_id, :category_id, :null => false
t.text :body
t.timestamps
end
리스트 5. 섹시해진 마이그레이션 코드
이렇게 눈에 띄는 부분 외에도 액티브 레코드가 개선된 부분이 많다. 특히나 성능에 대한 개선이 놀랍다. 미투데이 개발팀에서 레일스 2.0으로 올리고 꽤 빨라졌다면서 글을 올리기도 했다. 필자는 살포시 미투 버튼을 눌렀다. 속도를 향상시킨 일등 공신은 레일스 전반적으로 도입된 쿼리 캐시다. 한 요청안에서 같은 쿼리는 DB 쿼리를 다시 수행하지 않고, 이전 결과를 캐시해두었다가 반환한다. 물론 UPDATE, DELETE 쿼리가 수행되면 캐시를 무효화한다. 그리고 DB 컬럼에 저장된 값을 매번 루비 객체로 변환하는 오버헤드를 줄이기 위해 이 부분도 한번만 수행하도록 변경되었다. 특히나 시간을 처리하는 부분이 매번 루비 Time 객체를 만드느라 느리다는 평가를 받던 부분이었다.
가볍고 빠르지만 안전하게
기왕 성능 이야기가 나왔으니 조금 더 해보자. 레일스 1.2가 이전 버전(1.1)보다 느려졌다는 글이 블로고스피어에서 이슈가 된 이후로 레일스 코어팀이 성능 부분에 많은 신경을 썼다. 그 결과로 뷰 탬플릿을 찾는 로직을 매 요청마다 수행하던 것도 딱 한번으로 바꾸고, 뷰 파일이 변경되었나 확인하기 위해 stats 호출을 해보던 것도 기본으로 꺼버렸다. 테스트 중에서는 픽스처를 메모리에 올려두고 재활용해서 속도를 100% 빠르게 만들기도 했다.
뷰 부분을 보면 URL 도우미 함수를 호출하느라 많은 시간을 렌더링 하더라는 문제가 있었는데, 이 부분은 라우팅 최적화 모듈에서 매번 계산하는 것이 아니라 미리 계산된 값을 반환하는 단순한 메서드를 정의하는 방식으로 개선되었다. 단순히 pages_path, page_path(@page)등의 메서드를 호출하는 것은 성능을 전혀 떨어뜨리지 않는다는 의미다. 이런 노력이 있으니 레일스가 빠르다고 말하기는 뭐하지만, 애플리케이션을 개발하고 서비스하는데 충분하다라는 평가는 받을 수 있을 것 같다. 실제로 대부분 애플리케이션에서 나타나는 병목지점이 로직의 CPU 연산이 아닌 다른 부분이니까 말이다.
레일스 2에서는 보안 쪽도 꽤나 신경 쓰는 모습이다. 어설프게 몇 가지만 무용지물이었던 sanitize 메서드(크로스 사이트 스크립팅 공격을 막기 위해 HTML에서 위험한 태그등을 제거한다)는 화이트 리스트 기반의 강력한 필터링 메서드로 재탄생했다. 여기에 더해 CSRF(Cross-site request forgery) 공격을 막기 위해 코드도 기본으로 추가되었다. 폼에 숨겨진 값을 생성해서 넣고, 컨트롤러에서는 이 값을 검사하는 방식이다. 그렇게 하면 애플리케이션이 허용한 폼에서 온 입력임을 검증할 수 있다. HTTP 전용 쿠키도 지원하고 개발자가 조금만 더 신경을 쓰면 훨씬 안전한 사이트를 만들 수 있는 장치들이 많이 생겼다. 루비 온 레일스의 보안 문제만을 전문적으로 다루는 사이트(http://www.rorsecurity.info/)도 있으니 어느 정도 안심이 된다. 애플리케이션을 배포하기 전에는 이 사이트에 제공하는 Security cheatsheet나 필자가 쓴 레일스 보안 가이드를 꼭 한 번 읽어보기 바란다.
레일스가 주는 작은 즐거움
지금까지 설명한 것들 외에도 레일스 개발을 하다보면 알게 모르게 작은 개선점들을 만날 수 있을 것이다. 그 때는 즐겁게 웃으며 반겨주자. 아마 새로 추가된 initializers 디렉터리, 바뀐 뷰 파일 이름 짓는 법(action.mimetype.renderer), 같은 마임타입을 가졌지만 다른 형식을 서비스할 수 있게 된 점(예를 들어 iphone), rescue_from, Simply Helpful 플러그인, 프로파일러, 디버거, JSON 지원 강화 등이 독자 여러분을 기쁘게 해줄 것이다. 여기서 모두 설명해버리면 재미없으니 이쯤에서 새로운 기능에 대해 설명하는 것은 그만두기로 하자.
정리하는 뜻에서 레일스 2에서 가장 주목할 점을 한마디로 요약하면, MVC 프레임워크를 넘어서 리소스 기반 웹 프레임워크라는 새로운 비전을 제시하고 있다는 것이다. 이번에는 자신의 의견을 얼마나 주장할 수 있을지 무척 궁금해지는 대목이다. 그리고 필자가 레일스 2에서 높은 점수를 주는 부분은 기능 하나하나가 아니라 지난 일년여간의 개발 과정이다. 레일스 2는 조금 더 나은 코드를 만들기 위해 작은 개선을 꾸준히 해온 결과물이다. 열정과 애정이 있지 않으면 쉽지 않은 일이다. 우리도 업무에서 매일 매일 보는 코드를 이런 뜨거운 마음으로 대할 수 있으면 좋겠다.
이전 버전에서 업그레이드 하려면?
혹시 기존에 만들어둔 프로젝트를 2.0으로 업그레이드 할 생각이라면 몇 가지 주의할 점이 있다.
- URL에서 세미콜론이 빠졌으니 view 파일에서 혹시 pages/1;edit와 같은 URL을 하드코딩하지 않았나 살펴본다.
- 컨트롤러는 모두 복수형으로 써야한다. 일관성 때문이기도 하고, 문맥에 따라 재활용될 수 있게 하기 위해서이기도 하다. 혹시 단수(Singular) 리소스라며 단수형을 썼다면 이름을 바꿔야한다.
- ActionWebServices는 기본적으로 로드 패스에 포함되지 않는다. 사용하려면 명시적으로 포함해줘야한다.
- MySQL, SQLite, PostgreSQL를 제외한 다른 데이터베이스 어댑터는 모두 외부 gem으로 분리되었다. 필요하다면 설치해야한다.
- pagination이 레일스 코어에서 빠져 플러그인이 되었다.
- acts_as_list, acts_as_tree 등의 모듈이 플러그인이 되었다.
그 외에도 find_all 등의 메서드가 사용금지 되는 등의 변화가 있으므로, 꼭 콘솔의 경고 메시지를 유심히 살펴보도록 하자. 돌다리도 두들겨 보고 건너는 마음가짐으로 1.2 마지막 버전으로 업그레이드해서 한동안 잘 돌아가는 모습을 확인한 이후에 2.0으로 가면 좀 더 안정적일 수도 있겠다. 호환성 검사 도구(http://pastie.caboo.se/private/krcevozww61drdeza13e3a)도 있으니 돌려보자.
물론 새로 시작하는 프로젝트라면 당연히 2.0을 사용한다.
이어서 - (2) 레일스의 그늘과 대안 웹 프레임워크




February 26th, 2008 at 03:15 AM (myRuby.net) 항상 글 잘보고 있습니다~ 감사합니다 ^^
February 26th, 2008 at 03:29 AM 잘봤습니다 감사~
February 26th, 2008 at 04:32 AM (myRuby.net) 깔끔하게 정리된 내용을 보니..뭔가 다가오는 느낌.. 잘 읽었습니다.
February 26th, 2008 at 08:43 AM 글 잘읽었어요^^
February 26th, 2008 at 10:08 AM 작년에는 투자대비 효율이 너무 떨어졌는데 올해는 분발해서 손에 익혀야 겠어요. 항상 좋은 글 감사합니다.
February 26th, 2008 at 12:53 PM 감사합니다 ^^
February 27th, 2008 at 03:32 AM (myRuby.net) 잘봤소이다 ^^