스크립트 언어의 새로운 보석 루비 (Ruby)
속도가 빠르고 배우기 쉬운 인터프리팅 방식의 스크립트 언어 루비(Ruby). 그 힘과 인기가 IT의 본고장인 미 대륙을 넘어 점차 국내에까지 이르고 있다. 이런 루비의 상승세에 불을 댕긴 것은 바로 루비 온 레일스의 등장. 루비의 장점을 극대화한 이 오픈소스 프레임워크는 지난 2년의 시간 동안 자바, 닷넷 진영의 주류 프레임워크들을 긴장시키면서, 루비를 진정한 스크립트 언어의 보석으로 재탄생시키고 있다. 그렇다면 루비와 루비 온 레일스의 어떤 매력들이 이런 반향을 불러일으켰을까? 이번 스페셜 리포트에서 그 해답을 찾아보고, 아울러 웹2.0 시대에 어울릴만한 루비 온 레일스 활용법을 소개한다.
전도영 기자 mir@imaso.co.kr
more..
전통 언어의 혈통을 잇는 스크립트 언어, 루비
루비는 포스트 모던 프로그래밍 언어이다. 모더니즘이 순수한 이상을 추구한다면, 포스트 모더니즘은 여러 이상의 실용적인 결합을 추구한다. 루비는 기존 프로그래밍 언어의 장점을 받아들여 만들어진 무척 실용적인 프로그래밍 언어이다. 지금부터 필자와 함께 루비의 매력을 하나둘 짚어본다.
루비에 많은 영향을 준 언어에는 객체지향 언어의 아버지라 불리는 Smalltalk, 함수형 언어의 시조인 Lisp, 그리고 텍스트 처리에 뛰어난 Perl 등이 있다. 루비는 Smalltalk의 계보를 잇는 순수 객체지향 언어이지만, Lisp의 클로져(Closure)와 메타프로그래밍(Metaprogramming) 기능 또한 지원하며, Perl의 강력한 정규식 기능도 지원하고 있다. 어쩌면 프로그래밍의 각 패러다임을 대표하는 이들 언어가 하나의 교착점에서 만났다는 것이 바로 루비의 최대 강점일지도 모르겠다. 루비가 순수한 객체지향 언어라는 것은 루비에서 원시형 데이터 타입을 전혀 지원하지 않는다는 것을 의미한다. 루비에서는 모든 데이터가 곧 객체이다. 이러한 데이터 타입의 일관성은 루비의 신택스(Syntax)를 간소화시켰을 뿐만 아니라, 루비의 동적 타이핑(Dynamic Typing)과 맞물려서 루비로 작성된 코드를 매우 깔끔하게 만들어줬다. 루비에서는 클로져를 흔히 블록(Block)이라고 부르는데, 블록은 루비의 트레이드 마크와도 같은 기능이다. 블록은 배열 등과 같은 집합 데이터의 원소를 순차적으로 처리하는 작업 등에 특히 유용하며, 코드를 보다 간결하고 직관적으로 만들어주기 때문에 루비에서는 상당히 보편적으로 사용되는 기능이다. 최근에는 루비의 영향으로 자바에서도 클로져 기능을 추가하는 것이 논의되고 있을 정도다. 기존의 프로그래밍 언어와 차별화되는 루비의 가장 강력한 기능은 아마도 메타프로그래밍일 것이다. 메타프로그래밍이란 프로그램 코드를 작성하는 프로그램을 만드는 것을 말하는데, 보통 기계적이고 반복적인 코딩을 자동화하는 데 사용되는 매우 유용한 프로그래밍 기법이다. 어느 정도 루틴화 된 코드를 반복하여 작성하는 일은 개발자에게 지루한 작업일 수 밖에 없다. 메타프로그래밍은 이러한 코드를 아예 자동으로 생성하는 프로그래밍 기법으로, 루비에서는 메타프로그래밍을 통하여 런타임에 프로그램 코드를 동적으로 추가하는 것을 지원하고 있다. 이러한 루비의 메타프로그래밍은 C의 매크로 전처리나 C++의 컴파일 타임 템플릿 메타프로그래밍과는 대조적으로, 메타프로그래밍 코드 자체가 루비로 작성되며 컴파일 타임이 아닌 런타임에 프로그램 코드가 생성된다는 특징이 있다. 루비는 또한 텍스트 처리에 매우 뛰어난 프로그래밍 언어이다. 루비는 기본적으로 Perl의 텍스트 처리 기능을 그대로 계승하고 있는데, 이 때문에 루비의 텍스트 처리 능력은 Perl과 거의 동일하다고 말할 수 있다. 루비에서의 정규식은 내부적으로는 객체로 구현되어 있지만, 신택스 레벨에서는 Perl과 동일한 형태의 정규식 리터럴(literal)을 사용할 수 있다. 루비의 또 하나의 장점은 배우기에 무척 쉬운 언어라는 점이다. 루비처럼 강력한 프로그래밍 언어가 배우기 쉽다는 사실은 어쩌면 다소 아이러니컬하게 들릴지도 모르겠다. 하지만 루비를 만든 마츠모토 유키히로(Matsumoto Yukihiro)는 처음 설계할 때부터 단계적으로 익히기 쉬운 언어를 지향했다고 한다. 그 덕분인지 루비는 신택스가 매우 직관적일 뿐만 아니라, 완전히 마스터하기 이전에도 활용성이 비교적 뛰어난 언어이다. 처음부터 메타프로그래밍과 같은 고급 프로그래밍 기법을 알아야 할 필요는 없다. 루비는 기본적인 신택스만 알아도 범용 스크립팅 언어로 활용할 수 있기 때문에, 단계적으로 고급 기능을 익혀가면서 루비의 활용 범위를 점차 넓혀갈 수 있다. 루비의 직관적인 신택스가 루비를 처음 접하는 사람에게만 유용한 것은 아니다. 루비의 신택스는 직관적인 동시에 간결하기 때문에 루비로 작성된 코드는 가독성이 매우 뛰어나다. 실제로 루비 개발자 중에서 루비의 최대 장점으로 코드 가독성을 손꼽는 이들도 적지 않다. 앞에서는 몇가지 언어적 특성을 중심으로 루비를 개괄적으로 설명해 보았다. 이제부터는 코드 예제와 함께 루비의 이런 언어적 특성이 실제 코드에서 어떻게 반영되고 있는지를 살펴보고, 루비 개발 환경에 대한 내용도 일부 다뤄보도록 한다.
루비의 객체지향성
다시 말하지만 루비는 순수 객체지향 언어이다. 즉, 루비의 모든 데이터는 곧 객체이기 때문에 언제나 데이터에서 직접 메소드를 호출할 수 있다. 명령 프롬프트 창에서 irb를 실행시켜 다음을 한번 입력해 보자.
c:\> irb --simple-prompt
>> -124.abs
=> 124
>> 124.abs
=> 124
여기서 ‘abs’는 절대값을 리턴하는 메소드이다. 루비에서는 숫자 역시 객체이기 때문에 예로 든 경우처럼 숫자에 직접 메소드를 호출하는 것이 가능하다. 사실 루비 코드는 객체와 메소드로만 이루어져 있다고 해도 과언이 아니다. 루비에서는 심지어 클래스조차도 객체로 구현되어 있다. 루비에서 새로운 객체를 생성할 때는 일반적으로 클래스의 ‘new’ 메소드를 호출하는 방법이 사용된다.
>> t = Time.new
=> Sat Sep 16 18:28:05 KST 2006
>> 5 + 3
=> 8
>> 5.+(3)
=> 8
위에서 첫번째 코드는 숫자 5와 3을 더하라는 연산이고, 두번째 코드는 5라는 숫자 객체의 ‘+’ 메소드를 호출하여 숫자 3을 인자로 넘기라는 의미이다. 이 두 가지 코드는 완전히 동일한 코드로, 첫번째 코드는 두번째 코드를 호출하는 또다른 방법에 불과하다. 아울러 정수를 나타내는 Fixnum 클래스의 ‘+’ 메소드의 정의를 변경하면, 실제로 ‘+’ 연산자의 기능 자체를 바꾸는 것도 가능하다. 다음의 예를 한번 살펴보자.
>> class Fixnum
>> def +(other)
>> (self.to_s.concat(other.to_s)).to_i
>> end
>> end
=> nil
>> 5 + 3
=> 53
위에서 새롭게 정의한 ‘+’ 메소드는 각 숫자를 문자열로 바꾸어서 결합한 다음, 그 결과를 다시 숫자로 바꾸어 리턴하고 있다. 위의 코드는 클래스의 정의를 아무때나 변경할 수 있는 루비 클래스의 개방성을 보여주는 셈이다. 또한 루비는 자바와는 다르게 동적 타이핑(Dynamic Typing)을 지원한다. 이 때문에 객체에 어떤 메소드를 호출하는 것도 가능하다. 만약에 존재하지 않는 메소드가 호출되면, 컴파일 시점에 에러가 나는 것이 아니라 단지 런타임에 예외(Exception)가 발생할 뿐이다.
>> “gun”.length
=> 3
>> “gun”.fire
NoMethodError: undefined method `fire’ for “gun”:String
from (irb):3
이러한 루비의 동적 타이핑은 루비에서의 개발을 상당히 편리하게 만들어 주는 요소다. 이 덕분에 루비에서는 추상 클래스의 상속을 통하지 않고도 다형성(Polymorphism)을 실현할 수 있다. 이는 루비가 변수에 클래스 타입을 지정하지 않기 때문에 가능한데, 주어진 객체가 특정 메소드를 지원하는 것만 알고 있으면 그 객체가 실제로 어떤 클래스의 객체인지는 그다지 중요하게 생각하지 않는다. 이처럼 객체가 명시적으로 어떤 클래스에 속하느냐의 문제보다는 해당 객체가 어떤 메소드를 지원하느냐의 문제를 훨씬 더 중요시하는데, 이를 흔히 덕 타이핑(Duck Typing)이라고도 한다. 즉, 오리처럼 뒤뚱거리며 걷고 오리처럼 꽥꽥거리고 울면, 그 클래스 타입과는 상관없이 그냥 오리로 취급해도 무방하다는 식이다. 이 때문에 루비에서는 변수를 선언할 때도 클래스 타입을 명시적으로 지정하지 않고, 심지어 변수의 선언 자체도 자동으로 이뤄진다. 처음으로 변수에 값이 지정되는 시점에 그 변수가 자동으로 선언되는 것이다. 자바와 같은 스태틱 타이핑(Static Typing) 언어에 익숙한 개발자라면, 타이핑 측면에서 루비의 이러한 자유분방한 접근방식이 처음에는 다소 불안하게 느껴질 수도 있다. 하지만 이러한 접근방식에 조금만 익숙해지고 나면, 다이내믹 타이핑(Dynamic Typing)이 루비에서의 개발을 훨씬 더 편리하게 해준다는 점을 공감하게 될 것이다. 루비에서는 다중 상속의 복잡성을 피해가기 위해서 기본적으로 단일 상속 체계를 사용하고 있다. 하지만 루비는 실질적으로 다중 상속의 기능을 거의 다 지원하고 있는데, 이는 바로 모듈(Module)과 믹스인(Mixin) 덕분에 가능하다. 모듈은 일종의 불완전 클래스라고 말할 수 있는데, 모듈에는 멤버 변수와 메소드가 정의될 수 있다. 루비의 클래스는 단 하나의 부모 클래스만을 가질 수 있지만, 동시에 임의의 개수의 모듈을 포함(Mixin)할 수 있다. 믹스인은 바로 클래스에 모듈의 멤버 변수와 메소드를 추가하는 기능이다. 루비는 이처럼 모듈과 믹스인 기능을 통해 실질적인 다중 상속 기능을 실현하고 있다고 볼 수 있다.
블록
블록은 프로그램 코드를 데이터처럼 취급하게 해주는 기능이다. 즉, 함수를 정의하는 식으로 블록을 정의하여 다른 메소드에 이를 인자로 넘길 수 있다. 이처럼 코드를 데이터로 다룰 수 있는 것은 실로 매우 강력한 기능이다. 블록의 가장 보편적인 사용 케이스는 배열과 같은 집합 데이터의 각 원소에 특정 코드를 순차적으로 적용시키는 것이다. 다음의 코드를 한번 살펴보도록 하자.
>> [“apple”, “banana”, “orange”].map {|fruit| fruit.upcase}
=> [“APPLE”, “BANANA”, “ORANGE”]
‘{ ... }’ 부분이 바로 블록인데, 배열의 ‘map’ 메소드는 이 블록을 배열의 모든 원소에 적용시킨 후, 블록이 리턴하는 값을 가지고 새로운 배열을 만들어 리턴한다. 블록의 이러한 활용은 집합 데이터를 처리하는 데 필요한 코드의 양을 획기적으로 줄여줘 매우 유용한 코딩 기법으로 평가된다. 물론 집합 데이터를 처리하는 것이 블록의 유일한 용도는 아니며, 메소드에 알고리즘을 인자로 넘기는 경우나 이벤트에 콜백(Call back)을 걸어주는 경우에도 블록의 사용은 필요한 코드의 양을 상당 부분 줄일 수 있다.
메타프로그래밍
앞에서 설명한 것처럼 메타프로그래밍은 프로그램 코드를 생성하는 프로그램을 만드는 것이다. 다음의 코드를 한번 살펴보자.
>> class Person
>> def name
>> return @name
>> end
>>
?> def name=(name)
>> @name = name
>> end
>> end
=> nil
>> p = Person.new
=> #
>> p.name = “Kim Chul Soo”
=> “Kim Chul Soo”
>> p.name
=> “Kim Chul Soo”
위의 코드에서는 Person 클래스를 정의하고 있는데, ‘name’ 메소드와 ‘name=’ 메소드가 정의되어 있다. ‘name’ 메소드는 getter 메소드로 단순히 ‘@name’ 멤버변수를 리턴해주고 있고, ‘name=’ 메소드는 setter 메소드로 넘겨받은 인자를 ‘@name’ 멤버변수에 지정해주고 있다. 위의 코드에서는 Person 객체를 생성하여 앞에서 정의한 getter 메소드와 setter 메소드를 사용하는 것을 함께 보여주고 있다. 위와 같은 getter/setter 메소드의 정의는 매우 보편적으로 사용되는 코딩 패턴으로, 별도의 툴을 사용하지 않는 한 개발자가 매번 수작업으로 코딩할 수밖에 없었다. 루비에서는 메타프로그래밍을 사용하여 이 작업을 자동화할 수 있다. 다음의 코드를 보자.
>> class Person
>> attr_accessor “name”
>> end
=> nil
=> #
>> p.name = “Lee Young Hee”
=> “Lee Young Hee”
>> p.name
=> “Lee Young Hee”
&‘attr_accessor’는 일종의 클래스 메소드로 “name” 문자열을 인자로 받아 ‘name’ getter 메소드와 ‘name=’ setter 메소드를 런타임에 자동으로 생성해주고 있다. 이처럼 루비의 메타프로그래밍은 런타임에 새로운 메소드를 자동으로 추가하는 용도로 많이 사용된다. ‘attr_accessor’ 클래스 메소드는 상위 클래스로부터 Person 클래스로 자동으로 상속되고 있지만, 이런 메소드를 개발자가 직접 정의할 수도 있다. 다음의 코드를 한번 살펴보자.
>> class Person
>> def self.my_attr_accessor(str)
>> class_eval “def #{str}; @#{str}; end”
>> class_eval “def #{str}=(#{str}) @#{str}=#{str}; end”
>> end
>>
?> my_attr_accessor(“name”)
>> end
=> nil
>> p = Person.new
=> #
>> p.name = “Hong Gil Dong”
=> “Hong Gil Dong”
>> p.name
=> “Hong Gil Dong”
‘class_eval’은 문자열로 넘겨받은 루비 코드를 런타임에 실행해주는 메소드인데, 이 메소드가 바로 루비에서 메타프로그래밍을 가능하게 하는 핵심이다. 위에서 정의된 ‘my_attr_accessor’ 클래스 메소드는 넘겨받은 문자열에 매칭되는 getter/setter 메소드를 정의하는 루비 코드를 문자열 형태로 구성한 후, ‘class_eval’ 메소드를 통하여 이 코드를 실행한다. ‘my_attr_accessor’ 메소드는 런타임에 호출되고 있으며, 따라서 이들 getter/setter 메소드는 런타임에 동적으로 생성되어 프로그램의 실행 코드에 추가된다.
텍스트 처리
루비의 텍스트 처리 기능은 Perl을 그대로 벤치마킹했다. 여기서 주목할 부분은 루비가 Perl 스타일의 텍스트 처리 기능을 온전히 객체지향적으로 구현하고 있다는 점이다. 다음 코드를 한번 살펴보도록 하자.
>> if /([0-9]+)/ =~ “A year has 365 days.”
>> puts $1
>> end
365
=> nil
위의 코드를 보면 ‘/ ... /’ 부분의 정규식이 Perl과 동일한 형태로 사용되고 있는 것을 볼 수 있다. 루비에서는 위의 정규식 리터럴이 객체이며, ‘=~’는 이 정규식 객체의 메소드로 정의되어 있다.
>> /^([0-9]+)/.class
=> Regexp
>> /^([0-9]+)/.=~(“A year has 365 days.”)
=> 11
텍스트 처리에 있어 정규식의 주요 사용 케이스 중 하나는 바로 텍스트의 일부를 치환하는 작업이다. 루비에서는 문자열 객체의 ‘sub’ 메소드와 정규식 패턴을 사용하여 문자열의 특정 부분을 치환할 수 있다.
>> “banana”.sub(/a/, “@”)
=> “b@nana”
만약 매칭되는 모든 패턴을 치환하고 싶다면, ‘sub’ 대신에 ‘gsub’를 사용한다.
>> “banana”.gsub(/a/, “@”)
=> “b@n@n@”
이처럼 루비에서는 Perl의 텍스트 처리 기능을 객체지향적으로 구현하고 있으므로, 정규식의 사용이 Perl과 마찬가지로 편리하고, 코드의 가독성은 Perl보다 훨씬 더 뛰어나다고 할 수 있다.
왜 지금 루비인가
루비는 1990년대 말부터 그 저변이 꾸준히 확대되어 왔지만, 최근 들어 스포트라이트를 받게 된 결정적인 계기는 바로 레일스 프레임워크의 등장 때문이다. 레일스는 몇 가지 독창적인 아이디어와 루비의 강력한 기능이 절묘하게 결합되어 만들어진 웹 개발 프레임워크로, 이미 북미 지역에서는 레일스로 구축된 B2C 사이트만 해도 수 백 여개에 이르는 등 웹 개발 분야에 커다란 변화를 일으키고 있다. 레일스가 루비의 메인스트림 진입에 커다란 역할을 한 것은 사실이다. 하지만 여기서 주목할 것은 이제 루비는 레일스와 같은 주요 프레임워크가 그 기반으로 만들어질 수 있을 만큼 성숙했다는 점이다. 이미 레일스 말고도 수많은 애플리케이션과 프레임워크가 루비를 기반으로 만들어지고 있고, 이제 어떠한 개발 프로젝트에도 부족하지 않은 풍부한 라이브러리를 갖춰가고 있다. 레일스는 루비의 강점을 명확히 보여주는 프레임워크인데, 예를 들어 레일스에서의 웹 개발은 자바에 비해 적어도 5배 이상 더 빠르다. 레일스의 성공에 자극을 받은 다른 프로그래밍 언어에서도 레일스와 유사한 프레임워크를 구현하려는 시도가 잇따르고 있지만, 아직까지 이러한 시도는 그다지 성공적이지 못하였다. 이는 레일스가 루비만이 지원할 수 있는 고차원의 프로그래밍 기법을 십분 활용하여 설계된 프레임워크이기 때문이다. 레일스를 통한 마케팅 덕분에 루비는 지난 수년간 다양한 환경에서 그 효용성이 입증되어 왔다. 이제 루비를 익힌다는 것은 단순히 새로운 언어를 사용해보는 것을 넘어, 실무에서 활용하고 이력서에 추가할 수 있는 새로운 기술을 배우는 것이라고 할 수 있다.
루비 개발을 위한 툴
개발 프로젝트를 효율적으로 진행하기 위해서는 언어 이외에도 개발을 보조하는 다양한 툴들이 필요하다. 여기서는 루비 개발에 일반적으로 사용되는 몇 가지 툴을 소개한다. 프로그래밍에 있어 가장 중요한 툴은 텍스트 편집기이다. 윈도우 기반의 개발에서는 SciTE, UltraEdit, TextPad 등이 이미 루비 신택스를 지원하고 있으며, 레일스 개발에는 Eclipse 기반의 RadRails가 널리 사용되고 있다. 맥 OS X 기반의 개발에서는 TextMate, TextWrangler 등이 주로 사용되고 있으며, 유닉스 기반의 개발에서는 Emacs와 Vim 등이 루비를 지원하고 있다. 또한 루비는 라이브러리 관리 툴로 루비젬이라는 유틸리티를 사용하고 있는데, 이는 각종 루비 라이브러리를 자동으로 설치하고 관리해주는 핵심적인 유틸리티이다. 루비젬을 통해 새로운 라이브러리를 설치하는 과정은 다음과 같이 간단하다.
c:/> gem install xml-simple
Attempting local installation of ‘xml-simple’
Local gem file not found: xml-simple*.gem
Attempting remote installation of ‘xml-simple’
Successfully installed xml-simple-1.0.9
프로젝트의 빌드 툴로는 루비로 구현된 rake라는 툴을 사용한다. rake에서는 별도의 파일 포맷을 사용하는 대신에 루비로 구현된 내장형 DSL(Domain Specific Lan-guage)을 사용하고 있다. 이 덕분에 rake는 자바의 ant에 비해 빌드 파일 포맷이 훨씬 더 간결하면서도 더욱 강력한 기능을 자랑한다. RDoc은 API 문서를 자동으로 만들어주는 루비 유틸리티로 자바의 JavaDoc과 비슷한 역할을 한다. <화면 1>은 RDoc으로 생성된 루비 API 문서를 보여주는 웹 브라우저의 모습이다.

<화면 1> RDoc으로 생성된 루비 API문서
RDoc으로 만들어진 API 문서는 다음처럼 ri 유틸리티를 통해 조회할 수도 있다.
c:\> ri Array#join
---------------------------------------------------------- Array#join
array.join(sep=$,) -> str
---------------------------------------------------------------------
Returns a string created by converting each element of the array
to a string, separated by _sep_.
[ “a”, “b”, “c” ].join #=> “abc”
[ “a”, “b”, “c” ].join(“-”) #=> “a-b-c”
RDoc 문서는 보통 웹 브라우저에서 조회하지만, 간단한 확인이 필요할 때는 ri도 유용하게 사용될 수 있다. 루비젬, rake, rdoc처럼 기본적으로 제공되는 툴 외에도, 루비는 강력한 표준 라이브러리를 갖추고 있다. 다음에 보여지는 것처럼 ‘require’ 메소드를 써서 손쉽게 표준 라이브러리를 사용할 수 있다.
>> require ‘net/http’
=> true
>> response = Net::HTTP.get_response(“www.imaso.co.kr”, “/”)
=> #
>> response[“content-type”]
=> “text/html; charset=EUC-KR”
>> response[“last-modified”]
=> “Mon, 18 Sep 2006 02:34:31 GMT”
이는 ‘net/http’ 라이브러리를 사용하여 마이크로 소프트웨어 홈페이지에 접근하는 것이다. ‘response’의 ‘body’ 메소드를 호출하면, 해당 문서의 html을 직접 확인할 수도 있다. 아울러 루비에서 제공하고 있는 표준 라이브러리의 API 문서는 http://www.ruby-doc.org에서 확인할 수 있다.
엔터프라이즈 환경에서의 루비
국내에서는 이제 조금씩 루비에 관심을 갖는 단계이지만, 외국의 경우에는 이미 루비가 엔터프라이즈 환경에서 널리 사용되고 있다. 루비의 유연한 프로그래밍 모델이 복잡한 기술을 통합해 다뤄야 하고 고객의 요구에 따라 갑작스런 변화가 발생하는 엔터프라이즈 개발 환경에 여러가지로 유리한 면이 많기 때문이다. 오래전부터 루비를 엔터프라이즈 개발에 사용해온 대표적인 회사로는 ThoughtWorks를 꼽을 수 있다. ThoguhtWorks는 『엔터프라이즈 애플리케이션 아키텍처 패턴』, 『리팩토링』 등의 저서로 국내에도 널리 알려진 Martin Fowler가 CTO로 있는 다국적 IT 컨설팅 회사이다. ThoughtWorks는 그동안 각종 프로젝트에 루비를 사용함으로써 개발 시간을 현저히 단축시키고 많은 비용을 절감해왔다. 루비를 주요 엔터프라이즈 개발 언어로 사용하는 또다른 IT 컨설팅 회사에는 MomentumSI가 있다. MomentumSI는 웹서비스, SOA, 대용량 데이터 처리, 웹 애플리케이션, 프로토타입 개발 등의 분야에서 루비를 사용할 것을 적극적으로 권장하고 있다. 컨설팅 회사 이외에도 미국에서는 Amazon, EarthLink 등과 같은 많은 IT 기업들이 루비를 내부 및 외부 프로젝트에 사용하고 있다. Amazon은 과거 자체 전자상거래 엔진을 주로 Perl로 구현했지만, 최근 많은 부분을 루비로 바꿔 개발하는 것으로 알려졌다.
루비의 문제점
루비가 강력한 프로그래밍 언어인 것은 틀림없지만, 그렇다고 모든 면에서 완벽한 언어는 아니다. 여기서는 루비의 문제점을 살펴봄으로써, 특정 프로젝트에 루비가 적합한지 아닌지를 판단하는 데 도움을 주고자 한다. 루비의 첫번째 문제로 실행 속도를 꼽을 수 있다. 루비는 해석형 언어이기 때문에 실행 속도에 어느 정도 핸디캡이 있다. 실행 속도가 핵심적인 프로젝트에 루비를 사용하기 위해서는 최적화가 필요한 모듈은 C 언어로 개발해야 한다. 실제로 루비에는 필요한 모듈을 C로 개발할 수 있는 API가 포함되어 있다. 현재 루비 2.0이 가상 머신(Virtual Machine)을 기반으로 개발되고 있어, 머지않아 속도 문제가 상당 부분 해결될 전망이다. 여기에 최근에는 루비를 닷넷으로 이식하는 RubyCLR, Iron Ruby 등의 프로젝트도 활기를 띠고 있고, 썬마이크로시스템즈가 JRuby 프로젝트를 인수하는 등 실행 속도 문제를 개선한 계기가 계속 마련되고 있다. 흔히 지적되는 루비의 또다른 약점은 다소 제한적인 유니코드의 지원이다. 루비의 문자열 클래스는 현재도 유니코드를 지원하고 있지만, 몇 가지 필수적인 메소드가 바이트 단위에서만 작동한다는 문제가 있다. 이 문제는 기존 코드와의 호환성 유지 때문에 루비 2.0의 API 변경과 함께 다뤄질 예정으로 아직까지는 수정이 이뤄지지 않고 있다. 다만 이러한 유니코드 이슈는 외부 라이브러리를 사용하면 쉽게 해결되는 문제이므로, 이 때문에 루비의 사용을 미룰 필요는 없다.
special report 2 웹2.0을 위한 루비 프로그래밍
루비 온 레일스로 만나는 차세대 웹!
루비가 관심의 대상으로 부각된 데는 루비에 기반한 웹 애플리케이션 프레임워크인 루비 온 레일스(Ruby on Rails)가 기여한 바가 크다. 등장한지 불과 2년 여 밖에 되지 않은 오픈소스 프레임워크가 자바나 닷넷 등 기존의 다른 주류 프레임워크들의 틈 속에서 이렇게 인기를 누릴 수 있는 이유는 도대체 무엇일까? 루비 온 레일스 자체는 이미 몇 차례 소개된 바 있으므로 최근의 주요 트렌드인 ‘웹2.0’과 관련된 기능을 중심으로 루비 온 레일스의 진면모를 살펴본다.
흔히들 ‘웹2.0’이라는 것을 말할 때 ‘집단지능’이나 ‘소셜 네트워크(social network)’와 같은 단어를 이야기한다. 또는 복잡계 과학에 관심이 있는 사람이라면 좀 더 거창하게 ‘창발성(emergence)’이나 ‘작은 세상(Small World)’ 또는 ‘자기 조직화’와 같은 개념들로 이야기를 풀어나갈 지도 모르겠다. 물론, 필자처럼 소프트웨어를 만드는 일에 종사하는 사람들이라면 보다 실제적이고도 구체적인 블로그나 태깅 또는 Ajax와 같은 말들을 떠올리면서 이미 지긋지긋해 하고 있을 것이다.
웹2.0에 대해서는 이미 특집 등을 통해 여러 차례 다룬 적이 있고, 또 마소 독자라면 저마다 웹2.0에 대한 나름의 ‘철학’을 가지고 있을 테니, 여기서는 그냥 그런 것이다 정도로만 설명하고 넘어가자. <표 1>은 웹2.0이라는 화두를 던진 팀 오라일리(Tim O’Reilly)의 유명한 글 『웹2.0이란 무엇인가 - 다음 세대 소프트웨어를 위한 디자인 패턴 및 비즈니스 모델』에서 언급하고 있는 웹2.0의 특징과 그 특징들이 구체적으로 웹상에서 어떠한 기술들로 구현되는지를 필자 나름대로 한번 정리해 본 것이다. 여기서는 루비 온 레일스를 사용하여 이들 기술을 실제 구현해 본다.
루비 위에 깔린 기찻길, 루비 온 레일스
이제 루비 온 레일스로 넘어가 보자. 루비 온 레일스(Ruby on Rails, 이하 ‘레일스’)는 한마디로 말해 웹 애플리케이션 프레임워크(Framework)다. 좀 더 풀어 말하자면 루비 언어로 작성된 MVC 기반의 오픈소스 웹 애플리케이션 프레임워크라 할 수 있다.
모두가 알다시피 이미 세상에는 웹 개발 및 유지보수를 신속하고 효과적으로 할 수 있게 도와주는 많은 웹 애플리케이션 프레임워크들이 나와 있다. 자바 진영만 보더라도 Struts, Tapestry, Hibernate, Spring, JSF 등 일일이 꼽기 어려울 만큼 다양한 프레임워크들이 저마다의 용도와 장점으로 무장한 채 사용자들을 기다리고 있다. 심지어 마이크로소프트의 닷넷 프레임워크는 그 자체가 하나의 거대한 웹 애플리케이션 프레임워크이기도 하다. 이들 프레임워크들 중에는 상용인 것들도 있지만 소스가 공개된 것들도 많다. 물론 MVC 패턴을 따르는 프레임워크들도 넘쳐난다. 그렇다면 도대체 레일스가 다른 프레임워크들과 다른 점은 무엇일까?
고민은 잠시 접어두고, 우선 레일스 프레임워크의 구조를 간단히 살펴보도록 하자. 앞서도 말했지만 레일스는 MVC 기반의 프레임워크다. MVC는 모델과 뷰, 그리고 컨트롤러로 각각 역할이 구분된 구성요소들이 결합하여 어떤 일을 처리하는 아키텍처 패턴이다. 프레임워크에 따라 이들 세 구성요소 중 하나 또는 둘만을 담당하도록 만들어진 것도 있지만, 레일스는 이 세 가지 모두를 다룬다.
레일스에서 MVC를 담당하는 구성요소들은 각각 별도의 모듈(라이브러리)로 분리되어 있는데, 모델 부분을 담당하는 모듈은 액티브 레코드(Active Record)이고, 컨트롤러는 액션 컨트롤러(Action Controller) 그리고 뷰는 액션 뷰(Action View)라고 하는 모듈이 각각 담당하고 있다. 레일스에서 어떤 웹 요청이 웹 서버로 들어 왔을 때 그것을 처리하는 과정은 대략 <그림 1>과 같다. 예를 들어 http://localhost:3000/main/list와 같은 URL 호출을 수행하면 main이라는 컨트롤러 내에 있는 list라는 액션(action) 메소드가 실행되고, 이 액션 메소드는 다시 Post라고 하는 모델 객체를 통해 데이터베이스를 액세스하여 그 결과를 list.rhtml이라는 뷰를 통해 반환하는 것이다.

<그림 1> 레일스 프레임워크에서의 웹 요청 처리 과정
왜 레일스인가?
그렇다면 이제 왜 레일스일까로 다시 돌아와 보자. 어떤 프레임워크든 나름대로 특징과 장단점을 가지고 있을 것이다. 필자가 생각하기에 레일스의 가장 큰 장점은 생산성이다. 여기서의 생산성은 개발 시의 생산성은 물론이고, 이미 만들어진 애플리케이션의 유지보수에 대한 생산성도 포함한다. 레일스는 애자일(agile) 개발을 지원하며, 프레임워크 자체에서 개발 환경과 테스트 환경, 그리고 운영 환경을 구분하고 있다. 잘 갖춰진 테스트 프레임워크는 테스트 주도적인 개발을 쉽고 재미있게 해주며, 무엇보다도 동적인 언어(dynamic language)인 루비의 특성을 백분 활용함으로써 변화가 심한 개발 환경에서도 신속하게 적응할 수 있게 한다.
레일스의 또 한 가지 특징은 관례(convention)에 따르는 개발방식을 사용한다는 점이다. ‘구성 설정보다는 관례(Convention over Configuration)’라는 모토를 가지고 출발한 레일스 프레임워크는 개발자들이 레일스가 정해 놓은 관례(코딩 규칙이나 명명 규칙 등)를 따르기만 한다면 별도의 구성 설정을 거의 하지 않아도 되게 해 준다. 따라서 다른 프레임워크들에서 흔히 볼 수 있는 XML 파일 등을 사용한 복잡한 구성 설정의 부담이 많이 줄어들게 된다. 예를 들어, Post라는 모델을 만들 때 이 모델 객체와 맵핑된 실제 데이터베이스 테이블의 이름을 posts라고 주고 프라이머리 키를 id라고만 주게 되면 자동적으로 OR 맵핑이 이루어지는 것과 같은 식이다.
이 밖에도 DRY(Don’t Repeat Yourself) 원칙을 고수하여 코드의 중복을 최대한 줄이는 점, 동적인 언어인 루비의 리플렉션(reflection)과 메타프로그래밍(metaprogramming) 기능을 이용하여 유연하면서도 직관적인 코드를 만들어 내는 점도 특징이다.
레일스와 웹2.0
앞에서 간단히 소개한 레일스의 이런 측면들은 웹2.0 개발에 적용될 때 더 빛을 발하게 된다. 1년, 2년이 넘도록 계속해서 베타(beta) 서비스가 이루어지고, 그 속에서 사용자들의 요구를 즉시즉시 애플리케이션에 반영해 나가야 하는 상황에서, 레일스는 그런 니즈(needs)를 가장 잘 수용할 수 있는 프레임워크임에 틀림없다. 이는 레일스 프레임워크 자체가 하나의 웹2.0 애플리케이션을 만드는 과정에서 탄생한 것이라는 점에서도 짐작할 수 있다.
특히, 잘 갖춰진 테스트 프레임워크라든가, Ajax에 대한 부분을 프레임워크 속에 담고 있는 점, 그리고 무엇보다 액션 팩(Action Pack)과 액티브 레코드(Active Record), 액션 메일러(Action Mailer)나 액티브 웹 서비스(Active Web Service)와 같은 웹 개발의 필수 구성요소들을 모두 갖추고 있으면서 이들 구성요소에 관한 새 기능들을 필요에 따라 바로바로 붙였다 뗐다 할 수 있는 유연한 아키텍처 구조는 웹2.0 기반의 애플리케이션을 만드는 데 있어서 큰 도움을 준다. 레일스를 사용하여 구축된 대부분의 애플리케이션이 웹2.0 기반 애플리케이션들이란 사실은 이를 뒷받침해준다.
이제 레일스를 사용하여 앞서 설명한 웹2.0의 기능들을 구현해 보도록 하자. 간단한 블로그를 하나 만들고, 그 위에 RSS와 트랙백(trackback), 태깅과 같은 기능들을 구현해 볼 것이다. 물론 이런 블로그나 RSS, 또는 태깅이 있다고 해서 그 자체가 무조건 웹2.0이라고 말할 수는 없다. 하지만 적어도 웹2.0을 구현하는 데 효과적인 도구임은 분명하므로, 여기서는 이들 기능을 구현하는 데 초점을 맞추기로 하겠다.
간단한 블로그 만들기
웹2.0 애플리케이션 하면 빠지지 않고 등장하는 것이 바로 블로그(blog)일 것이다. 1인 미디어 시대를 창조한 주역으로서, 블로그는 이제 사람들의 생활에 없어서는 안될 필수적인 도구가 되었다. 여기서도 블로그를 하나 만들어 볼 것이다. 다만, 이 글의 주제가 레일스에서 웹2.0적인 부분들을 설명하는 것이므로, 튜토리얼(tutorial) 식의 접근은 하지 않을 것이다. 지면 관계상 핵심적이면서도 레일스에 특징적으로 연관된 부분만 언급하도록 하겠다.
사용자의 컴퓨터에 루비와 레일스, 그리고 MySQL과 같은 데이터베이스가 이미 설치되어 있다는 가정 하에, 명령행 프롬프트에서 다음과 같은 명령을 실행하여 레일스 애플리케이션을 하나 생성하자. 애플리케이션의 이름은 간단한(simple) 블로그라는 의미에서 simblog라 주었다.
>rails simblog
이 명령을 실행하면 <화면 1>처럼 디렉토리 구조가 새롭게 만들어진다.

이 디렉토리 구조가 바로 레일스 웹 애플리케이션의 골격을 이루는 디렉토리 구조로 레일스 애플리케이션의 배포단위가 된다. app 디렉토리 아래에 보면 controllers, models, views라는 디렉토리가 보인다. 이들 디렉토리가 바로 MVC 각각의 코드들이 들어갈 디렉토리이다.
이 상태에서 그 디렉토리 속에 모델 객체와 컨트롤러 객체, 뷰를 각각 만들어 넣으면 된다. 우선 모델 객체를 만들어 보자. 블로그를 만들고 있으므로 Post라는 모델을 하나 만들도록 하자. 그리고 Main이라는 컨트롤러도 하나 만들자.
simblog>ruby script/generate model Product
simblog>ruby script/generate controller Main
이제 데이터베이스(MySQL을 사용하는 것으로 가정한다)에 simblog_development라는 이름으로 데이터베이스를 하나 추가하고, 앞에서 모델을 만들 때 함께 만들어진 액티브 레코드 마이그레이션(migration) 파일을 열어 <리스트 1>과 같이 데이터베이스 스키마를 작성한다. 이후 명령행에서 rake migrate 명령을 실행하면, 모든 작업이 완료된다(여기서 rake는 유닉스의 make나 자바의 ant와 같은 루비의 자동화 관리 툴이다).
<리스트 1> db/migrate/001_create_posts.rb
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.column :title, :string
t.column :pub_date, :datetime
t.column :author, :string
t.column :contents, :text
end
end
def self.down
drop_table :posts
end
end
앞에서 script/generate 명령을 통해 생성한 모델 클래스와 컨트롤러 클래스의 내용은 각각 <리스트 2, 3>과 같을 것이다. Post라는 모델 클래스와 MainController라고 하는 컨트롤러 클래스가 각각 만들어졌다. 클래스를 열고 닫는 두 줄의 코드가 전부다.
<리스트 2> app/models/post.rb
class Post < ActiveRecord::Base
end
<리스트 3> app/controllers/main_controller.rb
class MainController < ApplicationController
end
이제 마지막으로 할 일은 컨트롤러에 액션(메소드)을 추가하는 것이다. 컨트롤러의 class 문과 end 문 사이에 <리스트 4>와 같이 한 줄 코드를 입력한 다음, 웹 서버(WEBrick)를 실행하고 브라우저를 열어 URL 창에 http://localhost:3000/main이라고 입력하면 <화면 2>와 같이 웹 애플리케이션이 작동되는 것을 볼 수 있다.
<리스트 4> scaffold 추가(app/controllers/main_controller.rb)
class MainController < ApplicationController
scaffold :post
end

<화면 2>는 비록 외양이 허술하기는 하지만, 그래도 posts 테이블에 대한 CRUD 기능을 갖춘 완전한 애플리케이션이다. 대부분의 웹 애플리케이션이 본질적으로 데이터베이스에 대한 CRUD 작업을 수행한다는 점을 감안하면, 이처럼 두세 번의 명령과 몇 줄의 코딩만으로 프로그램이 동작한다는 것은 주목할 만하다. 더 신기한 것은 만약 데이터베이스 테이블의 스키마가 변경된다고 하더라도 별도의 수정이 필요없다는 사실이다. 웹 서버를 재시작할 필요도 물론 없다. 바로바로 반영되어 화면에 표시된다.
이제 앞의 <그림 1>과 맞춰보면 레일스에서의 웹 요청 처리 과정이 한결 분명해질 것이다. 다만 뷰가 어디 있느냐라는 한 가지 의문이 생긴다. 그리고 또 한 가지, 컨트롤러에 대응되는 액션 메소드가 전혀 보이질 않는다. 이 부분이 바로 레일스의 다이내믹한 측면을 잘 보여주는 부분인데, 웹 요청을 받아 컨트롤러가 실행될 때 우리가 작성한 scaffold라는 메소드가 필요한 액션들을 동적으로 만들어 내는 것이다. 이 한 줄의 코드가 데이터베이스 테이블의 스키마 정보를 읽어서 입력 폼을 만들어 내기까지의 모든 과정에 필요한 코드들(예컨대, 액션 메소드와 뷰 코드)을 런타임에서 동적으로 생성한다. 코드를 생성하는 코드를 런타임에서 만들어 내고 바로 실행시키는 것, 바로 메타 프로그래밍인 셈이다.
RSS와 트랙백 구현하기
RSS 기능이 없는 블로그는 블로그가 아니라고 할 정도로, RSS는 블로그를 기존의 개인 홈페이지와 구별하는 중요한 구성요소다. RSS가 무엇이며 어떠어떠한 종류가 있고 또 각각 어떠한 형식을 가지는지 등은 이미 많이 알려져 있으므로, 여기서는 레일스에서 RSS를 구현하는 방법에 초점을 맞추기로 하자. ‘어떤 일을 하는 데는 한 가지 이상의 방법이 있다(There’s more than one way to do it)’는 Perl의 모토처럼, Perl의 전통을 다수 계승한 루비 기반의 레일스 역시 어떤 일을 처리하는 데는 반드시 한 가지 이상의 방법이 존재한다. RSS의 경우도 마찬가지다.
여기서는 앞에서 만든 Post 모델을 사용하여, 블로그에 RSS 배포(syndication) 기능을 추가해보기로 하자. 알다시피 RSS는 일정한 형식을 가진 XML 파일이며, 따라서 RSS 피드를 배포한다는 것은 방금 전 만든 Post 모델에서 내용을 추출하여 이것을 RSS 포맷에 맞는 XML 형태로 클라이언트 브라우저에 전송하는 것이다. 루비로 작성된 여러 가지 RSS 관련 라이브러리들 가운데 하나를 사용할 수도 있지만, 여기서는 가장 간단하게 레일스에서 제공하는 표준 뷰 템플릿을 이용할 것이다. 레일스에서는 XML 형태의 출력을 위해 흔히 빌더(Builder) 템플릿이라고 부르는 별도의 뷰 템플릿(view template)을 제공한다. 따라서 이 템플릿을 이용하면 쉽게 RSS 처리를 할 수 있다. 모델 객체는 이미 만들어졌으므로 컨트롤러에 RSS 배포를 위한 액션을 하나 만들고, 액션명과 동일한 이름으로 빌더 템플릿(rxml 템플릿)을 만들면 그만이다. 이제 브라우저의 URL 창에 http://localhost: 3000/main/rss라고 입력하면, 예상했던 대로 XML 형식의 RSS 파일이 출력될 것이다.
<리스트 5> rss 액션 추가(app/controllers/main_controller.rb)
def rss
@posts = Post.find(:all, :limit => 10) # 10개의 포스트만 추출
end
<리스트 6> app/views/main/rss.rxml
xml.rss('version' => '2.0') do
xml.channel do
xml.title "SimBlog RSS Feed Sample"
xml.link @request.protocol + @request.host_with_port + url_for(:rss => nil)
xml.description "this is a rss demo"
@posts.each { |post|
xml.item do
xml.title post.title
xml.link @request.protocol + @request.host_with_port
+url_for(:controller=>"main", :action => "show", :id => post.id)
xml.description post.contents
xml.pubDate post.pub_date
end
}
end
end
RSS와 함께 블로그를 좀 더 블로그답게 만들어 주는 기능이 바로 트랙백(trackback)이다. 어떤 블로그와 관련된 글을 자신의 블로그에 올리거나 혹은 타 블로그에 있는 자신의 (블로그와 관련된) 글들을 서로 볼 수 있게 함으로써, 블로그 간의 쌍방향 연결을 통해 집단지능을 키워나갈 수 있다는 것이 트랙백의 핵심이다.
이 트랙백은 기술적으로는 트랙백 핑(ping)이라고 하는 간단한 메시지를 HTTP POST 방식을 통해 상대방 블로그에 보낸다. 상대방 블로그에서는 이 POST 핑 요청을 받아 처리하고 그 처리 결과의 성공 유무를 간단한 XML 양식으로 반환한다. 따라서 트랙백의 구현은 결국 트랙백 핑을 보내는 트랙백 클라이언트 부분과 받은 트랙백 핑을 처리하는 트랙백 서버 부분으로 나뉘게 된다.
레일스에서 트랙백을 구현하는 것은 아주 쉽다. 아니 기본적으로 트랙백을 구현하는 것 자체가 그렇게 어려울 것이 없다. 여기서는 지면 관계상 트랙백 서버를 구현하는 부분만 살펴보기로 하자. 앞에서 우리가 만든 Post 모델 각각의 포스트에 대하여 트랙백을 달 수 있도록 하려면, Trackback이라는 모델을 하나 추가로 만들고, 이 Trackback 모델과 Post 모델 간에 연관관계(relationship)를 설정해 주어야 한다. Trackback 모델 역시 앞서 Post 모델을 만드는 것과 같은 방식으로 만들면 된다. 기존의 Post 모델에 <리스트 7>과 같이 한 줄만 추가하면 두 모델 간에 일-대-다 연관관계가 자동으로 만들어진다.
<리스트 7> 일-대-다 연관관계 추가(app/models/post.rb)
class Post < ActiveRecord::Base
has_many :trackbacks
end
이제 받은 트랙백 핑을 처리하는 일만 남았다. 트랙백 핑을 처리한다는 것은 HTTP POST를 통해 받은 POST 매개변수의 값들을 저장하고, 이어서 그 처리 결과를 간단한 XML로 반환한다는 의미다. Main 컨트롤러에 <리스트 8>과 같은 액션을 추가하면, 이제 이 블로그에 트랙백을 보내고자 하는 다른 블로거들은 http://yoursite.com/main/trackback/1과 같은 식의 트랙백 주소를 사용하여 이 블로그로 트랙백을 걸 수 있게 된다. 여기서 트랙백 처리 결과는 XML로 반환되는데, 처리가 성공하면 0, 실패할 경우 1을 반환한다. 간단한 XML이므로 별도의 뷰를 사용하지 않고 액션에서 직접 텍스트 문장 형태로 반환하는 방식을 사용했다.
<리스트 8> trackback 액션 추가
(app/controllers/main_controller.rb)
def trackback
if request.post?
tb = Trackback.new
tb.title = params[:title]
# ...
tb.save!
post = Post.find(params[:id])
post.trackbacks << tb
result = post.save ? 0 : 1 # 성공이면 0, 실패면 1
render :text => "<?xml version='1.0' encoding='utf-8'?>
<response><error><#{result}></error></response>",
end
end
<리스트 9> trackback 테스트 케이스(test/functional/main_controller_test.rb)
def test_post_trackback
post :trackback, { :id => 1,
:title => "sample trackback test",
:url => "http://myblog.net",
:excerpt => "Excerpt bla bla bla",
:blog_name => "My blog" }
assert_response :success
post = Post.find(1)
assert_equal 1, post.trackbacks.count
end
그럼 이제 이 트랙백이 제대로 작동하는지 확인해 보자. 앞에서도 언급하였듯이 레일스는 자체적으로 강력한 테스트 프레임워크를 제공하는데, 그 중 하나가 특정 컨트롤러의 동작을 테스트하는 기능 테스트(functional test)이다. 앞에서 컨트롤러를 생성할 때 이미 함께 만들어졌을 기능 테스트 파일을 열어서 <리스트 9>와 같은 테스트 케이스를 하나 추가하고, 이 테스트를 실행해 보면 테스트가 성공했다는 메시지가 출력될 것이다. 이 밖에도 레일스는 여러 컨트롤러와 모델들 간에 걸친 유스케이스나 사용자 스토리에 대한 통합 테스트(integration test)기능도 제공한다.
태깅 구현하기
태깅(tagging)이란 어떤 대상에 대하여 태그(tag)를 붙임으로써 기존의 카테고리에 의한 분류 방식이나 자동 검색이 가지는 한계를 극복하고, 태그의 공유를 통해 집단 지능을 높일 수 있도록 해 주는 새로운 콘텐츠 분류 및 검색 방법이다. 마찬가지로, 여기서도 태깅에 대한 상세한 설명은 생략하기로 하고, 바로 레일스로 태깅을 구현하는 부분으로 들어가 보자.
태깅을 위해 맨 먼저 할 일은 데이터 모델을 태깅이 가능한 구조로 만드는 것이다. 이를 위해서 태깅과 관련된 모든 데이터 모델들을 직접 설계할 수도 있지만, 바퀴를 새로 만들 필요는 없다. 레일스에는 이미 애플리케이션에 태깅 기능을 추가해 주는 여러 방법들이 존재하며, 여기서는 그 중의 하나인 acts_as_taggable 플러그인을 사용할 것이다. 참고로 플러그인은 레일스에서 새로운 개념을 확장하려 할 때 사용하는 하나의 작은 배포단위다(IDE인 이클립스의 플러그인과 같은 개념이다). 우선 다음과 같이 플러그인을 설치하자.
simblog>ruby script/plugin install acts_as_taggable
이렇게 하면 vendor라는 디렉토리 아래에 있는 plugins 디렉토리에 acts_as_taggable 플러그인이 설치된다. 다음으로는 acts_as_taggable에서 필요로 하는 데이터베이스 테이블의 스키마를 정의해 주어야 한다. 액티브 레코드 마이그레이션을 사용하여 <리스트 10>과 같은 마이그레이션 파일을 작성한 다음, rake migrate를 실행하면 모든 준비가 끝난다.
<리스트 10> acts_as_taggable을 위한 마이그레이션 파일
(db/migrate/003_add_taggable.rb)
class AddTaggable < ActiveRecord::Migration
def self.up
create_table :taggings do |t|
t.column :taggable_id, :integer
t.column :tag_id, :integer
t.column :taggable_type, :string
end
create_table :tags do |t|
t.column :name, :string
end
end
def self.down
drop_table :taggings
drop_table :tags
end
end
이제 태깅 기능을 추가할 대상을 선택할 차례다. acts_as_ taggable은 내부적으로 액티브 레코드에서 제공하는 다형적 연관(polymorphic association)이라는 기능을 사용하고 있다. 따라서 일단 acts_as_taggable이 설치되면 <리스트 10>의 마이그레이션을 통해 생성된 tags와 taggings라는 두 개의 테이블만으로, 태그를 걸고 싶은 어떠한 모델 객체에 대해서도 태깅 기능을 추가할 수 있다. 여기서는 Post 모델에 태깅 기능을 추가해 보자. <리스트 11>과 같이 Product 모델 클래스에 acts_as_taggable이란 코드만 추가하면 된다.
<리스트 11> acts_as_taggable 추가(app/models/post.rb)
class Post < ActiveRecord::Base
has_many :trackbacks
acts_as_taggable
end
이제 우리는 태깅 기능이 추가된 Product 모델을 사용할 수 있다. 물론, Product가 아닌 다른 어떤 모델 객체에 대해서도 클래스 정의에 이 acts_as_taggable만 추가시키면, 그 모델 객체는 그 순간부터 즉시 태깅 기능을 가지게 된다. 컨트롤러나 뷰에서는 이제 다음의 방법으로 Product 모델의 태그에 접근할 수 있다(여기서는 레일스에서 제공하는 console이라는 명령행 유틸리티를 사용하여 태깅을 테스트하고 있다).
simblog>ruby script/console
Loading development environment.
>> p = Post.find(1)
=> #<Post:0x3cb9188 @attributes={“title”=>”My First Post”, ...>
>> p.tag_with “ruby rails test” <-- (1) 특정 모델 객체에 태그 추가하기
=> [“ruby”, “rails”, “test”]
>> p.tag_list <--- (2) 특정 모델 객체에 붙은 태그 목록 보기
=> “ruby rails test”
>> Post.find_tagged_with “rails” <--- (3) 특정 태그를 가진 모든 레코드 추출
=> [#<Post:0x3c3f6e8 @attributes={“title”=>”My First Post”, ...>
>> tag_items = {}
=> {}
>> Tag.find(:all).each { |t| tag_items[t.name] = t.taggings_count }
=> [#<Tag:0x3e9de80 @attributes={“name”=>”ruby”, “id”=>”1”}, ...>
>> tag_items <--- (4) 태그와 태그 횟수를 해시(Hash)로 반환
=> {“rails”=>2, “beta”=>1, “rss”=>1, “test”=>1, “ruby”=>1}
여기서 맨 마지막의 tag_items는 루비 언어에서 지원하는 클로저(closures)를 이용하여 만든 것인데, 태그 구름(tag cloud)을 만드는 경우 등에 유용하게 사용할 수 있다.
Ajax와 RJS의 활용
Ajax는 웹2.0의 간판 스타다. 구글 맵 등을 통해 세상에 소개된 Ajax는 RIA(Rich Internet Application)를 위한 훌륭한 도구이다. 앞에서도 잠깐 언급하였듯이, 레일스는 프레임워크 차원에서 Ajax를 지원한다. Ajax 기술들을 사용하는데 필요한 거의 모든 것들이 이미 프레임워크 내부에 갖춰져 있어서, 개발자들은 몇 가지 간단한 메소드 호출만으로도 쉽게 Ajax가 제공하는 화려한 사용자 인터페이스나 비동기 HTTP 호출을 사용할 수 있다.
<리스트 12> 레일스에서의 Ajax 호출 예(app/views/main/ajax_test.rhtml)
<div>
<%= link_to_remote "Ajax 호출하기", :update => "result",
:complete => "Element. toggle($('result'))",
:url => {:action => "go_for_it"} %>
<div id="result"></div>
</div>
우선 간단하게 <리스트 12>를 보자. 이 리스트는 화면 출력을 위한 뷰의 일부분으로, 브라우저에서 보면 ‘Ajax 호출하기’라는 링크가 하나 출력된다. 이 링크를 클릭하면 go_for_it이라는 액션에 대하여 비동기 XMLHttpReqeust 호출이 일어나고, 그 액션의 처리 결과 반환받은 값으로 result라는 id를 가진 div의 innerHTML을 채우게 된다. 그리고 호출이 완료되면, result div를 토글(toggle)하게 된다. 만약 이 뷰를 브라우저에 출력된 상태로 소스보기를 통해 열어 보면, <리스트 13>과 같은 자바 스크립트 코드로 변환된 것을 알 수 있다. 이렇듯 레일스의 Ajax 지원 기능은 여러 가지 복잡한 Ajax 호출을 간단하게 만들어 준다. 참고로, 레일스는 내부적으로 Prototype과 script.aculo.us라고 하는 Ajax 지원 자바 스크립트 라이브러리를 래핑(wrapping)하여 프레임워크와 통합시켜 두었다.
<리스트 13> 소스보기를 통해 본 변환된 Ajax 호출
<div>
<a href="#" onclick="new Ajax.Updater('result', '/main/go_for_it',
{ asynchronous:true,
evalScripts:true,
onComplete:function(request){Element.toggle($('result'))}}
); return false;">Ajax 호출하기</a>
<div id="result"></div>
</div>
이 외에도 레일스를 사용하면 각종 비주얼 효과나 자동완성(autocompletion) 기능, 즉석 폼 편집(in-place form editing), 드래그 앤 드롭(drap-and-drop) 등과 같은 여러 가지 Ajax 기능들을 쉽게 구현할 수 있다.
Ajax와 관련하여 마지막으로 한 가지 소개하고 넘어갈 부분은 RJS(Ruby JavaScript)에 관한 것이다. 레일스는 기본적으로 세 가지 형태의 뷰 템플릿을 제공하는데, HTML을 생성하는 rhtml 템플릿, XML 파일을 만들어 주는 rxml 템플릿(이미 살펴 보았음), 그리고 JavaScript 파일을 만들어 주는 rjs 템플릿이 그것이다. HTML 파일과 XML 파일을 만드는 것은 일반적으로 모든 프레임워크에서 제공하지만, 자바 스크립트를 만든다는 것은 조금 생소할 수도 있다. <리스트 14>와 같은 템플릿을 하나 만들어 just_do_it.rjs라는 이름으로 views/main 디렉토리 아래에 둔다고 해 보자.
<리스트 14> app/views/main/just_do_it.rjs
page.insert_html :top, "result", "Hello RJS!!"
page.visual_effect :highlight, "result", :duration => 2
page.delay(2) do
page.visual_effect :shake, "result"
end
이제 <리스트 12>의 Ajax 호출을 실행하면, 어떤 일이 일어날까? XMLHttpRequest를 통해 just_do_it 액션, 그리고 그 액션의 뷰인 just_do_it.rjs가 호출되며, 이 파일은 레일스에 의해 서버에서 적절한 자바 스크립트 파일로 변환된 다음 브라우저로 전달된다. 따라서 브라우저에서는 ‘Hello RJS!’라는 문장이 result div에 출력되고, 이어서 그 문장이 2초 동안 노랗게 강조(highlight)되다가, 다시 2초가 지나고 나서는 한번 심하게 몸을 흔드는(shake) 효과가 생기는 것이다. 이게 바로 RJS다. 자바 스크립트마저도 루비 코드로 대체하는 것인데, 이것이 Ajax의 비동기 XMLHttp 호출을 통해 이뤄지게 되면, 한 번의 호출로 동시에 여러 가지의 Ajax 효과가 가능해진다. 기발하지 않은가?
REST 방식을 이용한 매쉬업(mashup) 구현
웹2.0과 관련하여 마지막으로 살펴 볼 기능은 웹 서비스에 관련된 것이다. 일반적으로 웹 서비스를 구현하는 방식에는 SOAP, XML-RPC, 그리고 REST의 세 가지 방식이 있다. 이 중 SOAP와 XML-RPC는 XML로 정의되어 있는 메소드(또는 함수)를 호출하는 방식이라는 점에서 서로 비슷하며, 특히 WSDL을 사용하는 SOAP의 경우 W3C에서 권장하는 방식이기도 하다. 하지만 보다 느슨한 연결(loosely-coupled)을 지향하는 웹2.0의 세계에서는 REST 방식이 인기를 얻는 것 같다.
레일스에서는 이 세 방식의 웹 서비스를 모두 지원하며, 특히 SOAP에 대해서는 복잡한 WSDL 작성 작업을 쉽게 해주는 액티브 웹 서비스(Active WebService)라는 라이브러리도 제공하고 있다. 그렇지만 주제가 주제인 만큼 여기서는 REST 방식을 통한 웹 서비스만 소개하도록 하겠다. <리스트 15>는 REST 방식을 통해 네이버의 블로그 검색 오픈 API에 접근하는 웹 서비스 클라이언트 예제다. 이런 식으로 REST를 사용하면 야후 맵이나 구글 검색과 같은 기존 서비스를 활용한 매쉬업을 쉽게 만들 수 있다.
<리스트 15> REST 방식을 통한 네이버 오픈API 접근(app/controllers/main_controller.rb)
def open_api
product = Product.find(params[:id])
query = CGI.escape(product.name)
access_key = "test"
url = "http://openapi.naver.com/search?key=#{access_key}&query=#{query}...
result = Net::HTTP.get(URI(url))
@doc = REXML::Document.new result
end
여기서는 웹 서비스 서버에 접속하기 위해 루비의 Net 라이브러리와 REXML 라이브러리를 사용하고 있지만, 조만간 액티브 리소스(Active Resource)라고 하는 REST 전용 라이브러리가 레일스에 포함될 것이라고 하니, 이 새로운 레일스 전용의 REST 모델을 사용하면 레일스에서의 REST 개발이 한층 더 쉬워지지 않을까 예상해 본다.
여기서 다루지 못한 기능들 중에서도 웹2.0과 관련된, 아니 굳이 웹2.0과 관련된 것이 아니라 하더라도, 미처 다루지 못한 레일스의 훌륭한 기능들이 아직도 많이 남아 있으므로 관심 있는 독자들은 참고자료를 활용하길 바란다.
웹2.0, 루비, 그리고 개발자
이 글을 시작하면서 필자는 루비가 인기를 얻게 된 데는 레일스의 공이 컸다고 말했다. 그런데 실은 그 반대도 성립한다. 즉, 레일스가 인기를 얻게 된 데는 루비 언어의 공이 컸다는 것이다. 누군가가 프로그래밍 언어는 이미 머릿속으로 생각한 프로그램을 표현하는 도구가 아니라, 아직 존재하지 않는 프로그램을 생각해 내기 위한 도구이며, 좋은 프로그래밍 언어는 마치 유화 물감처럼 생각을 번복하는 것을 용이하게 만들어 주어야 한다고 말하던 것이 떠오른다. 필자는 루비와 레일스가 바로 그런 화가의 유화 물감 같은 것이 아닐까 생각한다. 적어도 웹2.0 개발 환경에서만큼은 그렇다.
블로그로 시작했으니 블로그로 끝을 맺자. 다나 보이드(dan ah boyd)라는 사람의 블로그에 쓰여 있는 글인데, 독자 여러분도 루비와 웹2.0을 함께 떠올리면서 음미해 보길 바란다.
“그 다음에 할 일은 사용자의 관점에서 시스템을 설계해서 구현하는 것입니다. 사용자의 입장을 용인하는 것만으로는 부족합니다. 새로운 관점을 만들어 내는 이들은 바로 사용자입니다. 그렇기 때문에 우리의 기술에는 그들의 관점이 반영되어야 하는 것입니다. 우리가 사용자에 대해 불평만 해댄다면 정작 중요한 것을 잃게 될 겁니다. 사용자는 뭔가를 증명하기 위해 기술을 사용하는 게 아닙니다. 자신의 틀에 들어맞기 때문에 사용할 뿐입니다.”
special report 3 액티브 레코드 vs 하이버네이트 레일스의 꽃,
액티브 레코드 집중 분석
필자는 주로 스프링과 하이버네이트 등 자바 기반의 POJO 프레임워크를 이용하여 서비스를 개발해왔는데, 루비와 레일스를 사용해본 후 이 둘의 매력에 푹 빠져 버렸다. 그리고 최근 새 서비스 개발을 위해 다양한 자바 프레임워크와 레일스 등을 비교 검토한 결과, 최종적으로 레일스를 쓰기로 결정하였다. 이 결정에는 여러 이유가 있었지만 레일스의 퍼시스턴스 프레임워크인 액티브 레코드가 큰 비중을 차지했다. 3부에서는 이 액티브 레코드에 대해 설명한다.
최근 루비 언어와 루비 온 레일스(Ruby on Rails, 이하 ‘레일스’) 프레임워크에 대한 관심이 늘고 있다. Martin Fowler, Bruce Tate, David Geary 등 많은 유명 컨설턴트, 개발자들이 레일스에 대해 호의적으로 언급하고, 레일스를 이용한 상용 서비스들이 속속 등장함에 따라 레일스에 대한 신뢰도 또한 높아지고 있다. 특히 다수의 자바 관련 서적으로 졸트상을 수상한 Bruce Tate는 최근 루비 언어의 우월성을 주장하는 『From Java to Ruby』라는 문제작(?)을 출간하기도 했다.
레일스만의 매력
사실 레일스의 컨트롤러와 뷰 단을 담당하는 ActionPack의 아키텍처 자체는 Webwork의 아키텍처와 크게 다르지 않다(물론 동적 언어인 루비의 특성으로 인한 다양한 이점이 존재하며, RJS 등 통합된 웹2.0 지원은 매우 편리하다). 하지만 퍼시스턴스를 담당하는 액티브 레코드는 자바 진영의 가장 대표적인 퍼시스턴스 프레임워크인 하이버네이트와 전반적인 아키텍처가 달라 지적인 측면에서 흥미로웠고, 사용하기에도 훨씬 편리했다. 실제로 필자는 2년 간 내부 서비스에서 상용 결제 시스템에 이르기까지 하이버네이트를 사용해 왔고, 하이버네이트의 생산성 및 안정성에 꽤 만족하고 있다. 하지만 액티브 레코드를 학습한 직후, 액티브 레코드에 대한 기본적인 지식밖에 없는 상태에서도 액티브 레코드를 이용한 개발이 더 재미있고 개발 속도도 배 이상 빠른 느낌을 받았다. 또한 라이브러리 자체의 확장이 거의 불가능한 하이버네이트에 비해 액티브 레코드는 사용 목적에 맞게 라이브러리를 쉽게 수정하거나 확장할 수 있다는 점도 마음에 들었다.
필자의 경험에서 볼 때 같은 기능의 새로운 웹 서비스를 만들 경우 레일스의 전반적인 생산성이 JEE의 그것보다 뛰어나다. Bruce Tate는 프로젝트 경험을 통해 생산성이 8배 정도 우월하다고 주장한다. 하지만 레거시 연동이나 크리티컬한 엔터프라이즈 시스템 구축 등에는 JEE가 레일스보다 우수하다할 수 있다. 또한 스크립트 언어인 루비의 특성 상 소스코드가 공개될 수밖에 없기 때문에 상용 소프트웨어 솔루션을 레일스로 만드는 데는 무리가 있다. ‘은탄환은 없다’란 말대로 모든 면에서 뛰어난 해결책은 찾기 어려울 것이다. 모든 선택에는 트레이드 오프가 있게 마련이니까. 그러므로 두리뭉실 ‘레일스가 우수하다, 혹은 JEE가 우수하다’와 같은 소모적인 논쟁 대신, 수행하려는 프로젝트의 목적과 성격을 검토해 본 후 올바른 해결책을 선택하는 것이 현명한 방법일 것이다.
물론 올바른 선택을 하기 위해서는 당연히 선택의 대상을 이해하고 비교할 수 있는 능력이 필요하다. 여기서는 폭발적인 인기를 얻고 있는 레일스의 핵심 모듈 중 하나인 액티브 레코드와 자바 진영의 대표적인 퍼시스턴스 프레임워크인 하이버네이트를 비교분석하여 개발자들에게 선택에 필요한 정보를 조금이나마 제공하고자 한다.
ORM 소개
본격적으로 두 프레임워크를 비교하기에 앞서 ORM(Object/ Relational Mapping)이 무엇이고 어떤 문제를 해결해 주는지 간단히 살펴보고 넘어가기로 하자. 객체 지향 세계에서는 시스템을 객체와 객체들 사이의 관계를 이용하여 프로그래밍 한다. 반면, 관계형 데이터베이스에서는 데이터를 집합 이론을 이용하여 저장하고 처리한다. 대부분의 시스템은 데이터베이스를 다루게 되는데 이때 객체 세계와 집합 세계의 근본적인 철학의 차이로 인해 많은 문제가 생기게 된다. 구체적으로 열거해 보면 다음과 같다(이 내용은 Gavin King과 Christian Bauer가 쓴 『Hibernate in Action』의 1장에서 잘 설명하고 있으므로 관심 있는 독자는 시간을 내어 읽어 보길 바란다).
● 입자도(granularity)의 차이
● 서브 타입 문제
● 아이덴티티(identity) 문제
● 연관 관계와 관련된 문제
● 객체 그래프 네비게이션 문제
초기에는 이러한 차이를 개발자들의 노고로 해결하였으나 이곳 저곳에서 많은 불만이 쏟아져 나왔다. 필자 역시 이를 위한 간단한 라이브러리를 만들어 사용하며, 이러한 문제를 해결해 줄 수 있는 프레임워크가 나오기를 기다릴 정도였다(물론 이러한 문제를 해결하기 위한 다양한 방법이 현재 개발되고 있다).
그 중 하나가 객체 지향 데이터베이스인데 데이터베이스가 객체 지향 개념을 이용하여 데이터를 다루는 것이었다. 하지만 성능, 개발자 풀 등의 여러 문제로 인해 초기의 목적을 달성하지 못한 것으로 판단된다. 현재의 많은 데이터베이스들이 객체-관계형 데이터베이스를 표방하지만 실무에서는 관계형 데이터베이스 기능만을 이용하는 것이 대부분이다.
다른 해결 방법은 컴퓨터공학의 전통적인 해결 방법인 추상화를 도입하는 것이었다. 즉 프로그래밍 단에 객체와 관계형 데이터베이스를 매핑해 주는 새로운 레이어를 도입하고, 프로그래머는 이 레이어를 이용하여 객체지향적으로 데이터베이스 프로그래밍을 하는 것이다. 이 방법은 큰 성공을 거두었고, 현재 ORM이라 통칭된다. 우리가 살펴보려는 액티브 레코드와 하이버네이트 모두 이러한 역할을 하는 ORM 프레임워크이다.
본격적인 내용에 들어가기에 앞서 꼭 짚고 넘어가야 하는 것이 있다면 ORM이 거창한 것이 아니라는 사실이다. ORM의 가장 큰 목적은 개발자들이 좀 더 쉽고 직관적으로 데이터베이스 프로그래밍을 할 수 있도록 해주는 것, 그 뿐이다. 더 구체적으로 말하면 ‘DAO(Data Access Objects)를 얼마나 편하게 만들 수 있느냐?’이다. ORM을 사용하지 않아도 DAO를 통해 유연하고, 확장성 있는 시스템을 얼마든지 만들 수 있다. 또한 DAO를 사용하는 클라이언트는 DAO 구현체가 ORM을 사용했는지 아니면 JDBC 등을 이용해 직접 코딩했는지 알지 못한다. 다만 같은 기능을 구현할 때 걸리는 시간과 들어가는 노력이 다를 뿐이다.
물론 지연된 로딩(Lazy Loading)과 캐싱 등을 통해 성능을 향상하는 등의 다양한 이슈가 있지만, ORM의 기본적이며 가장 중요한 목적은 ‘보다 편리하고 빠른 데이터베이스 프로그래밍’, 보다 세련되게 표현해 ‘기민한 데이터베이스 프로그래밍’을 하는 데 있다는 사실을 명심하길 바란다. 그러면 이러한 관점에서 두 프레임워크를 비교해 보기로 하자.
아키텍처
앞에서 이야기했듯이 액티브 레코드와 하이버네이트의 아키텍처가 매우 다른 방식으로 구성되어 있어 흥미롭다. 액티브 레코드는 이름이 의미하듯 액티브 레코드 패턴을 실체화하고 있고, 하이버네이트는 Data Mapper 패턴을 실체화하고 있다. 우선 액티브 레코드 패턴의 정의는 <그림 1>과 같다. 그리고 Data Mapper 패턴의 정의는 <그림 2>와 같다.

Person이란 객체를 액티브 레코드 패턴을 사용하여 실체화 했다면 <리스트 1>과 같이 사용하게 될 것이다. 이때 find 등의 연산은 특정 로우(객체)가 아닌 테이블(클래스)을 대상으로 하는 연산이므로 static 메소드로 구현하게 되며, update는 특정 로우를 대상(객체)으로 하므로 인스턴스 메소드로 구현하게 된다(물론 bulk 연산인 update_all이 있다면 static 메소드로 구현해야 할 것이다).

<리스트 1> 자바 버전과 루비 버전
/* 자바 버전 */
Person person = Person.find(1); // 테이블에서 PK가 1인 로우를 로딩
person.setFirstName("Song");
person.update(); // person 객체를 이용하여 해당 로우를 업데이트
# 루비 버전 - 액티브 레코드 예제이다.
person = Person.find(1) # find는 클래스 메소드
person.first_name = "Song"
person.update # update는 인스턴스 메소드
반면에 <리스트 2>는 Data Mapper 패턴을 사용한 것이다. 이 코드는 실제 하이버네이트를 사용하는 예를 보여준다.
<리스트 2> Data Mapper 패턴의 이용
/* session과 같은 별도의 Data Mapper가 필요함
Session이 범용 Data Mapper이다. */
Session session = sessionFactory.openSession();
// session을 이용하여 데이터베이스 조작
Person person = (Person)session.load(1);
person.setFirstName("Song");
session.update(person);
session.close();
액티브 레코드 패턴과 Data Mapper 패턴을 비교해보자. 전반적인 사용은 액티브 레코드 패턴이 편리하다. 별도의 매퍼 없이 모델 객체를 조작하면 되기 때문이다. 이때 Person과 같은 모델 객체는 그 자체가 DAO가 된다. 이때 Person에 CRUD 메소드(create, read, update, delete) 뿐 아니라 getTexableEarnings와 같은 메소드도 추가할 수 있다. Data Mapper 패턴은 여러 데이터베이스를 이용할 경우 연결 관리나 트랜잭션 관리 등에 편리하다. 하지만 DAO를 별도로 작성해야 하는 부담이 따른다. 이러한 불편을 극복하기 위해 하이버네이트는 Generic DAO 패턴을 소개(http://www.hibernate.org/328.html)하는데, 이는 액티브 레코드 패턴과 상당히 유사한 점이 많다. 하지만 POJO 당 하나의 DAO를 만들어야 하는 점과 DAOFactory 관리 및 스프링 설정 파일 관리는 여전히 부담으로 다가온다.
OR 매핑 방법
액티브 레코드에서는 ‘테이블 이름이 클래스 이름의 소문자 복수형’이라는 등의 몇 가지 약속(convention)만 지키면 별도의 설정이 필요 없다. 그 예는 <표 2>와 같다.

테이블 이름은 소문자, 복수형이고 여러 단어로 구분되어 있을 경우는 ‘_’를 구분자로 사용한다. 이러한 특징은 레일스 전반에 흐르고 있는 ‘Convention over Configuration’이란 철학이 반영된 것이다. 만약 people이라는 테이블이 있고, 객체를 통해 테이블에 데이터를 저장하고 싶다면 다음과 같이 Person 클래스를 선언하면 된다(보통은 ‘script/generate model Person’식으로 스크립트를 실행해 생성한다).
class Person < ActiveRecord::Base
end
매우 단순하지 않은가? ActiveRecord::Base를 상속한 것을 제외하면 어떠한 코드도 없다. 심지어 객체의 속성도 선언하지 않았지만, persons 테이블의 컬럼명을 Person 클래스의 속성으로 사용할 수 있다. persons 테이블에 first_name 컬럼이 있다면, person.first_name과 같이 사용할 수 있는 것이다. 액티브 레코드::Base를 상속하기만 하면 액티브 레코드 패턴을 실체화할 수 있으니 상당히 편리하다.
그런데 만약, 테이블 이름으로 person을 사용할 수밖에 없는 경우에는 어떻게 해야 할까? 액티브 레코드는 이러한 경우를 대비하여 추가 설정이 가능하도록 지원한다. Person 클래스를 person 테이블에 매핑하고 싶다면 다음과 같이 설정해주면 된다.
class Person < ActiveRecord::Base
set_table_name “person”
end
반면, 하이버네이트에서는 .hbm.xml 확장자를 가진 설정 파일을 이용하여 매핑한다. 이때, 하이버네이트에서 제공하는 Ant 태스크를 이용하여 클래스 파일에서 설정 파일을 생성할 수 있고, 또한 테이블을 이용하여 설정 파일을 생성할 수 있다. 반대로 설정 파일에서 클래스 파일과 테이블을 생성할 수도 있다. 즉 클래스 파일, 설정 파일, 혹은 테이블 가운데 하나만 있으면 나머지를 생성할 수 있다. 이를 정리하면 <그림 3>과 같다.

하지만 클래스 파일과 sql 파일 혹은 테이블로부터 설정 파일의 다양한 옵션을 생성하기란 어렵다. 따라서 필자의 경우는 hbm 설정 파일을 기준으로 삼아 작업한다(xdoclet을 이용하여 클래스 파일을 기준으로 삼을 수도 있다). 예를 들면 <리스트 3>과 같다.
<리스트 3> hbm 설정 파일을 기준으로 한 예
publc class Person{
private Long id; // PK를 위한 것
private String firstName;
private String secondName;
// 기타 속성들…
// getter, setter들…
}
[Person.java]
<class name="package.Person" table="people">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="firstName" type=vString" column="first_name"/>
<property name="secondName" type="String" column="second_name"/>
</class>
</hibernate-mapping>
관계
ORM 툴의 가장 막강한 기능 중 하나가 바로 객체 간의 관계를 데이터베이스에 저장하거나, 데이터베이스에 저장되어 있는 정보를 객체 간의 관계에 매핑하여 가져오는 것이다. 그리고 액티브 레코드와 하이버네이트 모두 이러한 기능을 충실히 지원한다. 하이버네이트와 비교해 이를 정리하고(<표 3> 참조), 액티브 레코드 예제를 살펴보기로 하자.
이제 레일스 홈페이지의 위키 페이지에 있는 포럼 예제를 살펴보기로 하자.
<리스트 4> 코드 예(forum.sql)
CREATE TABLE 'categories' (
'id' int(11) NOT NULL auto_increment,
'name' varchar(50) DEFAULT NULL,
PRIMARY KEY ('id')
);
CREATE TABLE 'forums' (
'id' int(11) NOT NULL auto_increment,
'category_id' int(11) DEFAULT NULL,
'name' varchar(50) DEFAULT NULL,
'description' varchar(100) DEFAULT NULL,
PRIMARY KEY ('id')
);
CREATE TABLE 'messages' (
'id' int(11) NOT NULL auto_increment,
'type' varchar(50) DEFAULT NULL,
'forum_id' int(11) DEFAULT NULL,
'author_id' int(11) DEFAULT NULL,
'topic_id' int(11) DEFAULT NULL,
'title' varchar(255) DEFAULT NULL,
'content' text,
'written_on' datetime DEFAULT NULL,
PRIMARY KEY ('id')
);
위와 같은 테이블이 있을 때 <리스트 5>와 같이 모델 클래스를 만들면 된다. 이때, ERD에 virtual로 표시되어 있는 Topics와 Replies는 실제로는 없는 테이블이다. 물리적으로는 messages 테이블이 존재하며, ‘단일 테이블 상속’을 통해 Topics와 Replies를 사용한다. <리스트 5>의 모델 클래스에서 Topic과 Reply는 Message 클래스를 상속하고 있으며, 액티브 레코드는 내부적으로 messages 테이블의 type 컬럼 값을 통해 이들의 타입을 구분한다. type 컬럼에는 Message, Topic, Reply 등 해당 클래스명이 들어가게 된다. 하이버네이트도 설정파일에서 구분자(discriminator) 설정을 통해 상속을 처리할 수 있게 해준다.
<리스트 5> 모델 클래스 구현
class Author < ActiveRecord::Base
has_many :replies
end
class Category < ActiveRecord::Base
has_many :forums
end
class Forum < ActiveRecord::Base
belongs_to :category
has_many :topics
end
class Message < ActiveRecord::Base
has_one :author
end
class Topic < Message
belongs_to :forum
has_many :replies
end
class Reply < Message
belongs_to :topic
end
이제 모델을 활용해 보자. ‘Book’이란 이름(name)의 카테고리에 있는 포럼을 찾고 싶다면 다음과 같이 한다. 이때 category는 forums 테이블의 category_id를 이용하여 해당 포럼들(forums)을 찾게 된다. 이 역시 약속(convention)이며, 추가로 설정이 가능하다.
@category = Category.find(:all, :conditions => [“name = ?”, “Book”])
@forum = category.forums
만약 카테고리에서 시작해서 토픽의 저자를 찾아 출력하고 싶다면 다음과 같이 한다.
@category = Category.find(:all, :conditions => [“name = ?”, “Book”])
@category.forums.each do |forum|
forum.topics.each do |topic|
print topic.author.name # 토픽에서 저자를 찾아 이름을 출력한다.
end
end
아울러 Music이라는 카테고리를 만들고, 이 카테고리에 Music Forum을 만들고 싶다면 다음과 같이 하면 된다.
@category = Category.new(“name” => “Music”)
@category.forums << Forum.new(“name” => “Music Forum”,
“description” => “Forum for Music”)
@category.save # forum도 함께 저장된다.
@category.delete # category 로우만 지워지고, forum은 지워지지 않는다.
이와 비슷한 코드를 하이버네이트를 이용해 작성하면 다음과 같다.
Session session = sessionFactory.openSession();
Category category = new Category(“Music”);
category.getForums().put(new Forum(“Music Forum”, “Forum for Music”));
session.save(category); # 올바르게 설정했다면 포럼도 저장
session.delete(category); # casacade=“all”로 했다면 forum 로우도 지워짐
처음에는 루비 언어의 문법이 생소하겠지만, 익숙해지면 간결한 문법에 오히려 매력을 느낄 것이다. 위의 내용을 하이버네이트를 이용하여 구현하는 것은 독자분들을 위한 과제로 남긴다. 설정 파일까지 셈한다면 액티브 레코드를 사용했을 때에 비해 전체 라인수가 상당히 늘어나겠지만, 어노테이션과 Generic DAO를 이용하면 상당히 수월해질 것이다.
쿼리 언어
ORM을 사용하면 데이터베이스의 퍼시스턴스 레이어에 쿼리를 보내게 되므로 별도의 쿼리 언어를 도입하는 것이 일반적이다. 하지만 두 프레임워크는 이 부분에서 상당히 다른 접근을 취하고 있다. 액티브 레코드의 쿼리 언어는 몇 가지 편리한 기능이 있지만 기본적으로 SQL과 다를 바 없다. conditions는 where 절, order는 order by 절에 해당한다.
person = Person.find(:first) # select * from people로 가져온 첫번째 객체를 리턴
persons = Person.find(:first, :conditions => [ “user_name = ?”, user_name])
persons = Person.find(:first, :order => “created_on DESC”)
반면 하이버네이트는 HQL(Hibernate Query Language)라는 새로운 쿼리 언어를 만들었다. HQL이 SQL과 흡사하기는 하지만, 다른 부분도 적지 않다. 특히 HQL은 데이터베이스 중립적인 언어로 표현되며, 설정한 Dialect를 통해 각 데이터베이스에 최적화된 SQL로 변환되어 실행된다. HQL을 사용할 때는 테이블이 아니라 클래스(객체)에 질의한다는 사고가 필요하다.
List persons = session.createQuery(“from Person”).setMax Results(1).list();
persons = session.createQuery(“from Person person where person.user_name
= :user_name).setString(“user_name”, username).list();
persons = session.createQuery(“from Person person order by person.created_on DESC).setMaxResults(1).list();
하지만 하이버네이트를 사용하다 보면 가끔씩 하이버네이트의 HQL 파서 때문에 애를 먹을 수 있다. 이에 대해서는 http:// blog.naver.com/scroco/50003559019를 참조하길 바란다. 마지막으로 SQL을 사용할 수 밖에 없는 경우가 있게 마련이다. 이러한 경우를 대비하여 액티브 레코드는 find_by_sql 메소드를, 하이버네이트는 Native SQL을 사용할 수 있도록 지원한다.
확장성
ORM 프레임워크를 확장하여 새로운 기능을 추가하고 이를 재사용할 수 있다면 좋을 것이다. 액티브 레코드는 루비 언어의 믹스-인(Mix-in) 기능을 이용한 플러그인을 통해 이러한 확장을 가능하게 해준다. 예를 들어 트리 자료 구조는 메뉴 등 여러 곳에서 재사용할 수 있다. 액티브 레코드는 이를 위해 acts_as_tree를 지원한다. 다음과 같이 사용하면 된다.
class Menu < ActiveRecord::Base
acts_as_tree :order => “name”
end
acts_as_tree를 이용하면 children이라는 속성을 이용하여 자식들을 조작할 수 있다. 단, acts_as_tree를 사용하려면 해당 테이블에 부모의 PK를 저장하는 parent_id란 이름의 컬럼이 필요하다(물론 다른 이름으로 설정 가능하다). 트리 구조의 부서 조직을 표현하면 <리스트 6>과 같다.
<리스트 6>
root = Menu.create(:name=>"부서")
development = root.children.create(:name => "개발")
marketing = root.children.create(:name => "마케팅");
payment_development = development.children.create(:name => "페이먼트 개발")
web_development = development.children.create(:name => "웹 개발")
domestic_marketing = marketing.children.create(:name => "국내 마케팅")
foreign_marketing = marketing.children.create(:name => "해외 마케팅")
이러한 기능은 모델을 재사용할 수 있다는 점에서 생산성 측면에 큰 도움이 된다. 이 외에도 레일스는 순서있는 리스트를 쉽게 표현할 수 있는 acts_as_list를 기본적으로 제공하며, 태깅 기능을 추가해 주는 acts_as_taggable, 코멘트를 붙일 수 있게 해주는 acts_as_commentable, 가격을 붙일 수 있게 해주는 acts_as_ pricible 등의 오픈 소스 플러그인을 포함한다. 해당 플러그인이 요구하는 컬럼만 추가하면 유의미한 모델 기능을 편리하게 재사용할 수 있고, 또한 편리한 플러그인을 계속 추가할 수 있다. 이와 같은 확장 기능은 루비의 개방성과 믹스-인 기능 덕분이며, 하이버네이트에서는 볼 수 없다. Bruce Tate의 『From Java to Ruby』에 담긴 인터뷰에서 레일스를 만든 David Heinemeier Hansson는 다음과 같은 이야기를 했다.
‘자바 모델에서는 개발자들이 프레임워크를 변경하려 시도조차 하지 않을 것이다. 예를 들어 하이버네이트는 Gavin King이 만드는 무엇이고, 스트럿츠는 몇 년 동안 동결되어 있다. 크기와 복잡성이 그 원인이다. 하지만 루비 커뮤니티에서는 그렇지 않다. 프레임워크를 확장하거나 수정하는 일이 쉽기 때문에 누구라도 적절한 시간 안에 이러한 작업을 수행할 수 있다. 우리는 레일스를 처음 사용한 날 밤에 패치에 기여한 많은 사람들을 알고 있다. 자바 프로젝트의 경우 하이버네이트, 스트럿츠 혹은 스프링이 정확히 원하던 것이 아니어서 문제가 되는 경우가 있다. 하지만 루비는 패치를 통해 액티브 레코드 혹은 레일스를 수정할 수 있다. 그리고 그 비용은 매우 적다. 우리는 모든 레일스 프로그래머들이 이러한 과정을 통해 프로젝트에 기여하길 바란다.’ - Bruce Tate의 『From Java to Ruby』140p
성능 튜닝과 트랜잭션
ORM은 지연된 로딩(Lazy Loading)과 캐싱 등을 이용하여 성능을 향상시킬 수 있도록 도와준다. 앞에서 살펴본 코드를 다시 한번 보도록 하자.
@category = Category.find(:all, :conditions => [“name = ?”, “Book”])
@forum = category.forums
첫번째 줄에서 category를 가져올 때 forum은 가져오지 않으며, forum은 실제로 사용하는 시점에 질의를 해서 데이터를 가져온다. 이는 forum을 사용하는 빈도가 적을 때는 성능 향상에 도움이 될 수 있다. 하지만 forum을 항상 사용한다면, 오히려 질의를 한번 더 하는 꼴이 될 것이다(이를 N+1 문제라 한다). 하지만 액티브 레코드에서는 항상 지연된 로딩을 하기 때문에 현재로서는 이를 해결할 방법이 없다. 반면 하이버네이트는 설정 옵션을 통해 이 문제를 해결할 수 있다.
또한 하이버네이트는 다양한 캐싱 전략을 이용하여 불필요한 DB 액세스를 줄일 수 있지만, 액티브 레코드는 아직 이러한 기능을 제공하지 않는다. 트랜잭션 처리에 있어서도 하이버네이트는 여러 데이터베이스에 걸친 글로벌 트랜잭션 처리(Two-phase commit)를 지원하지만, 액티브 레코드는 지원하지 않는다(흉내낼 수는 있다).
테스트
테스트에 있어서 레일스는 매우 쾌적한 환경을 제공한다. 애초에 애플리케이션을 product, development, test 환경으로 분리하여 실행시킬 수 있도록 제공할 정도다. 그리고 자체에 JUnit과 비슷한 테스트 프레임워크를 제공하며, Fixture를 이용하여 테스트 데이터를 파일에 넣어 저장한 후 기본적인 연산 테스트에서 비즈니스 로직이 담긴 연산까지 손쉽게 테스트할 수 있다. 하이버네이트를 이용하여 비슷한 레벨의 테스트를 하려면 더 많은 시간과 노력이 요구된다.
로깅
액티브 레코드의 쿼리 언어는 앞에서 보았듯이 사실상 SQL이기 때문에 로깅 정보에 SQL 쿼리를 남길 수 있다. 반면 하이버네이트는 HQL을 SQL로 자동 변환하여 실행하기 때문에 정보가 직접적이지 않다. 또한 SQL을 출력하도록 설정 파일을 수정하더라도 하이버네이트가 내부적으로 PreparedStatement를 이용하기 때문에 설정한 파라미터 값 대신 ‘?’이 찍혀 나온다. 이 값까지 볼 수 있도록 설정하면 쿼리 하나마다 거대한 트리가 출력되므로 별 도움이 되지 않는다. 그래서 필자는 JDBC를 직접 사용할 경우에는 PreparedStatmenent를 래핑하여 로깅 정보를 찍을 수 있도록 구현(Decorator 패턴을 사용)한 Loggable PreparedStatement를, 하이버네이트 사용 시에는 http://ww w.p6spy.com에서 p6spy를 다운로드 받아 이용한다.
기타 편리한 기능들
마지막으로 앞에서 살펴보진 않았지만 유용하게 사용할 수 있는 액티브 레코드의 기능을 설명하기로 한다. 우선 유효성 검증을 도와주는 수많은 헬퍼들이 있다.
<리스트 7> 유효성 검증을 위한 헬퍼들
class Person < ActiveRecord::Base
# first_name 필드의 최고 길이를 30으로 제한한다.
validates_length_of :first_name, :maximum=>30
# last_name의 길이를 30으로 제한하고, 위반한 경우
# :message에서 설정한 에러 메시지를 돌려준다.
validates_length_of :last_name, :maximum=>30,
:message=>"글자수가 %d보다 작아야 합니다."
end
이 예는 길이를 검증하는 방법을 보여준다. 이 외에도 valida te, validate_on_create, validate_on_update, validates_accep tance_of, validates_associated, validates_confirmation_of, validates_each, validates_exclusion_of, validates_format_of, validates_inclusion_of, validates_length_of, validates_ numericality_of, validates_presence_of, validates_size_of, validates_uniqueness_of 등의 많은 헬퍼들이 있으므로 유효성 검증을 손쉽게 처리할 수 있다.
또한 insert, update, save, delete 연산 전후에 호출되는 메소드나 객체를 등록할 수 있는 콜백(callback) 기능, 그리고 여기서 한걸음 더 나아간 옵저버(Observer) 기능을 제공한다. 단순한 콜백 사용 예를 살펴보자(<리스트 8> 참조). 이 콜백은 특정 토픽을 삭제하기 전에 해당 토픽의 부모를 먼저 삭제하도록 해준다.
<리스트 8> 단순한 콜백 사용 예
class Topic < ActiveRecord::Base
before_destroy :destroy_parents
def destroy_parents
self.class.delete_all "parent_id = #{id}"
end
end
앞에서 다음과 같은 코드를 보았을 것이다. 콜백을 이용하면 category 삭제 시, forum도 연쇄적으로 삭제할 수 있다.
@category.delete # category 로우만 지워지고, forum은 지워지지 않는다.
생산성 차이가 그 핵심
지금까지 액티브 레코드와 하이버네이트 전반에 대해 비교했다. 필자는 개인적으로 액티브 레코드는 경쾌하고, 하이버네이트는 묵직하며 정교하다란 생각을 가지고 있다. 액티브 레코드는 개발이 빠르고 편리한 반면, 하이버네이트는 레거시 연동에서 엔터프라이즈 환경에 이르기까지 광범위한 폭을 어우르는 전천후 ORM이기 때문이다.
그런데 이들 두 프레임워크 사이에는 엄연한 생산성 차이가 존재한다. 무엇 때문일까? 이는 루비와 자바, 바로 두 언어의 차이에서 비롯된다. 액티브 레코드의 생산성은 루비의 클로져, 동적 타입, 선택적 파라미터, 오픈 클래스, 메시지 전달 및 missing_ method 처리 메커니즘 덕분이다. 루비는 진정 액티브 레코드, 더 나아가 레일스의 생산성을 빛내주는 보석이며, 자바 기반의 JEE는 당분간 이러한 생산성 차이를 극복하지 못할 듯 싶다.

rails_take2_with_sound.part1.rar


