Flying Mate

'루비'에 해당되는 글 6건

  1. 2008/04/23 SimpleDB with RightAws (2)
  2. 2008/02/24 짓궂은 이야기 (4)
  3. 2008/02/24 Rails Caching
  4. 2008/02/23 레일스의 클래스 확장 방식
  5. 2008/02/14 자바 개발자를 위한 레일스 (4)
  6. 2007/12/04 프레임워크에 대해(2): Prototype.js

SimpleDB with RightAws

소소하지 않은 일상 2008/04/23 09:32 by FlyingMate

notice: 같은 글을 스프링로그에도 적었는데, 그 쪽이 더 보기 편하다.
update: 마침 RightScale의 투자유치 소식이 들린다.

모델 하나를 ActiveRecord에서 SimpleDB로 전환하였다.
RightScale사의 RightAws Gem을 이용하였다. 유효성 검증(Validation)과 다른 모델과의 연관관계(Association)에 대한 고민은 일단 미뤄두고, 모델과 SimpleDB 사이의 가장 기본적인 연결을 목표로 하였다.

간단한 모델이라서 간단한 작업일 줄 알았는데 생각보다 손이 많이 갔다. ComplexDB라고 이름 붙이고 싶을 정도다. SimpleDB 자체가 복잡해서가 아니라 그 동안 ActiveRecord가 ORM으로서 너무나 많은 것들을 해주고 있었기 때문에, 기존의 기능들을 포기하지 않으면서 SimpleDB를 사용하려다보면 신경써야 할 부분이 많아진 것이다.

우선 Gem을 설치하고, config/environment.rb에서 RightAws Gem을 require한다. Amazon SimpleDB 계정에 연결하는 코드도 담는다.


sudo gem install right_aws

# config/environment.rb
require 'sdb/active_sdb'
require 'sdb/right_sdb_interface'
RightAws::ActiveSdb.establish_connection(access_key, secret_key)


도메인(데이터베이스에서의 테이블)을 만들어야 하는데 애플리케이션 배포시 한 번만 만들어주면 되므로 애플리케이션 코드에 넣지 않고 irb쉘에서 생성해주었다.


irb> require 'sdb/active_sdb'
irb> require 'sdb/right_sdb_interface'
irb> RightAws::ActiveSdb.establish_connection(access_key, secret_key)
irb> class Product < RightAws::ActiveSdb::Base
irb>   set_domain_name :products
irb> end
irb> Product.create_domain


set_domain_name 은 해당 모델의 도메인 이름을 명시해주는 부분인데, irb나 루비파일에서 도메인을 생성할 때, set_domain_name을 해주지 않고 Product.create_domain을 바로 하면 디폴트로 product라는 이름의 도메인이 생성된다. 반대로 레일스 환경(ruby script/console 포함)에서 Product.create_domain을 해주면 products라는 복수형의 도메인이 생성된다.

RightAWS는 ActiveSupport의 로드 여부를 파악해서 로드되어있으면 복수형의 도메인명을 디폴트로, 로드되어 있지 않으면 단수형을 디폴트로 잡는다. 즉 레일스 환경이 아니더라도 require 'activesupport'를 통해 복수형 도메인명을 디폴트로 사용할 수 있다. 복잡하다 싶으면 set_domain_name을 사용하면 된다. 아무래도 ActiveRecord의 컨벤션에 익숙해지다보니 복수형이 마음이 편하다.

그 다음은 모델 클래스를 재정의하는 것이다. 위의 irb에서도 domain을 생성하기 위해 모델 클래스를 잠깐 정의했는데, 위와 동일한 형태로 레일스 애플리케이션의 모델 코드에서 사용할 수 있다. class Product < ActiveRecord::Base로 시작되던 코드를 class Product < RightAws::ActiveSdb::Base로 바꾼다.

나는 좀 다르게 사용했는데 RightAws를 모델이 바로 상속하지 않고, 중간에 다른 클래스가 RightAws를 상속하고, 그 클래스를 모델이 상속하는 형태로 구성해보았다. 틀림없이 RightAws의 코드를 수정하거나 메서드를 다시 정의하거나 새롭게 추가해야 할 일이 생길 것이기 때문이다. RightAws가 플러그인이 아니라 젬이기 때문에 라이브러리 코드를 직접 수정하지 않는게 좋고, 모델들에 공통적으로 적용할 내용을 개별 모델 코드에 중복해서 작성하는 것도 피하기 위해서이다.

중간에 마음대로 만지작거릴 수 있는 클래스를 하나 만든 것인데, active_item.rb파일을 lib에 담아두었고, 그 파일 안에 ActiveItem 모듈과 Base 클래스를 정의했다. 아래에 전체 코드가 있다. 좀 정돈이 안 되어있고 미완성이지만 동작하기는 한다. SimpleDB를 본격적으로 사용하게 된다면 좀더 가꾸어볼 생각이다. 스카폴드로 만들어진 컨트롤러와 뷰에서는 코드 수정 없이 SimpleDB를 적용할 수 있도록 find메서드와 destroy, update_attributes, to_s 정도를 다시 정의했다.

module ActiveItem
  class Base < RightAws::ActiveSdb::Base    
    @@attributes = []
    class << self
      def attributes    
        if @@attributes.blank?
          first = find(:first)
          @@attributes = first.attributes.keys.select {|attr| attr != 'id'}
        end
        @@attributes
      end    

      def find(*args)
        result = super(*args)
        result.is_a?(Enumerable) ? result.each(&:reload) : result.reload
        result
      end

      def find_all_by_(format_str, args, limit=nil)
        results = super(format_str, args, limit)
        results.each(&:reload)
        results
      end

      def find_by_(format_str, args)
        result = super(format_str, args)
        result.reload
        result
      end    
    end  

    def method_generated
      @@method_generated ||= false
    end

    def method_generated=(value)
      @@method_generated = value
    end

    def method_missing(method_id, *args)
      method_name = method_id.to_s.match(/\w+/)[0]
      if !self.method_generated
        self.class.attributes.each do |attr|
          method_body = <<-EOV
            def #{attr}
              self[:#{attr}]
            end

            def #{attr}=(value)
              self[:#{attr}] = value
            end
          EOV
          self.class.class_eval(method_body, __FILE__, __LINE__)
        end
        self.method_generated = true
      end

      if self.class.attributes.include?(method_name)
        send(method_id, *args)
      else
        super
      end
    end

    def destroy
      self.delete
    end

    def update_attributes(attrs)    
      save_attributes(attrs)
    end

    def to_s
      id.to_s
    end  
  end
end

모델에서는 ActiveItem을 상속한다. ActiveItem 정의부에서 모델에 필요한 것들 몇 가지를 대신 해주었기 때문에 기존의 ActiveRecord를 상속했을 때와 마찬가지로 모델 코드가 간결해졌다.

class Product < ActiveItem::Base
end

Database와 SimpleDB의 차이점을 이해할 필요가 있다. 일단 스키마 정의가 따로 없기 때문에, 어트리뷰트 목록을 어떻게 받아올지 결정해야 한다. 모델 코드에 어트리뷰트를 명시해줄 수도 있고, 별로 좋은 방법은 아닌 것 같지만 위에서처럼 데이터 하나를 가져와 어트리뷰트 목록을 파악한 후 클래스 변수에 담아둘 수도 있다.

스키마 정의가 없고, 레코드마다 어트리뷰트 목록을 다르게 저장할 수 있긴 하지만 AWS 도큐먼트를 보면 인덱싱과 데이터 검색을 위해서 어트리뷰트 목록을 레코드 전체에 걸쳐 일관되게 사용하기를 권하고 있다. 가령 자동차 정보를 입력할 때, 차가 있을 경우 {:car => "Bugatti"}로 저장하고, 차가 없을 경우 :car 어트리뷰트를 아예 두지 않을 수도 있지만 웬만하면 {:car => "None"}으로 저장해서 탐색 시간을 줄이라고 말한다.

또 다른 큰 차이점은 Database에는 String, Text, Integer, Boolean, Datetime 등의 데이터 타입이 있는데 반해 Document-Oriented Datastore인 SimpleDB는 모든 데이터가 String이라는 점이다. 데이터베이스에서 당연했던 개념이 SimpleDB에서는 번거로운 문제가 되는데 바로 Integer와 Datetime이다.

Integer 형이 String이 되면 자리수가 다른 두 수 사이의 대소관계가 달라지게 된다. Integer 세상에서는 3 < 12 이지만 String 세상에서는 '3' > '12' 가 되기 때문에 새로운 문제가 발생한다. SimpleDB는 이를 위해 Zero-padding을 사용한다.

꽤 큰 자리수까지 0을 채워서 String 대소 관계를 Integer 대소관계와 동일하게 만들어주는 것이다. '000003' < '000012' 이렇게. 음수 데이터를 저장해야 하는 경우는 충분히 큰 수를 모든 데이터에 더해서 양수로 저장한다. 음수를 그대로 저장하면 '-32' < '-54'이 되기 때문이다. 꺼내서 사용할 때는 더했던 수를 다시 뺀다.


Datetime의 경우 ruby에서 Time.now를 곧바로 데이터로 저장하면 Tue Apr 22 23:06:26 +0900 2008 형태가 된다. 데이터베이스에서는 문제 없이 전후를 구분해낼 수 있지만, SimpleDB는 시간 순서를 전혀 파악하지 못한다. 때문에 ISO8601로 변환해서 저장해야 해야 하는데(Time.now.iso8601), 그렇게 되면 2008-04-22T23:06:26+09:00 와 같이 년월일시분초 순서로 저장되어 문자열 비교만으로 전후 관계를 찾아낼 수 있다.

다 해결이 된 듯 하지만 또 다른 문제를 야기하는데, 레일스의 Datetime Select 헬퍼에서는 ISO8601 형태의 시간 데이터를 자동으로 읽어내거나 자동으로 저장하지 못하기 때문에 헬퍼를 새로 정의하거나 편집 단계에서 일시적으로 원래의 Datetime형태(Time.parse를 이용)로 바꾸어 사용해야 한다.

크게 어려운 과제는 아니지만, 기존에는 뷰 헬퍼가 어떻게 동작하는지 알 필요 없이 Datetime과 select 테그를 손쉽게 연결할 수 있었는데, SimpleDB를 사용하려면 헬퍼의 지저분한 내부구현을 파악해야 하고 고려해야 하니 머리가 아파진다. 문제가 되지 않았던 것들이 문제가 되니, 정녕 이것을 사용하는 것이 옳은 것인가 여러 번 회의하게 되기도 한다.

기술적으로 가장 큰 차이라고 말할 수 있는 부분은, 데이터베이스에서 일반적으로 한 칼럼에 하나의 데이터를 넣었던 반면(Denormalization을 위해 여러 개 넣기도 하지만) SimpleDB의 어트리뷰트에는 기본적으로 배열이 저장된다는 점이다. 위의 모델은 다른 모델과 연관관계가 없었기 때문에 특별한 점이 없었지만, 다대다 관계를 정의할 때 일상적으로 만들어줘야 했던 연관 테이블이 SimpleDB에서는 필요 없게 된다.

이 때문에 RightAws에서는 어트리뷰트를 저장하는 메서드가 두 가지다. save와 put. save는 덮어씌우기이고 put은 추가하기이다.

@product = Product.find(:first)
@product[:link] = "naver.net"
@product.save #=> {:link => ["naver.net"]}
@product[:link] = "daum.com"
@product.put #=> {:link => ["naver.net", "daum.com"]}
@product[:link] = "cyworld.org"
@product.save #=> {:link => ["cyworld.org"]}

친구 목록을 저장해야 된다면 friendships 테이블을 만드는 대신, 아래와 같이 어트리뷰트에 친구들의 user.id를 몽땅 집어넣어 사용할 수 있다.

@user[:friend_ids] = "3"
@user.put
@user[:friend_ids] = "5"
@user.put
@user.reload
puts @user[:friend_ids] #=> ["3", "5"]

save_attributes와 put_attributes도 save/put과 같은 관계이다. 대신 ActiveRecord에 있던 update_attributes가 없어졌기 때문에 ActiveItem에서 update_attributes를 save_attributes로 연결해 두었다. 위에 언급한대로, 다대다 등 필요한 경우에는 put_attributes를 사용하면 된다.

뷰나 컨트롤러, 헬퍼 메서드 라이브러리를 수정하지 않고 form_for 메서드를 그대로 사용하려면 몇 가지 작업할 부분이 있다. 기본적인 스카폴드와 ActiveRecord에서는


<% form_for(@product) do |f| %>

이렇게만 해 주어도 new 탬플릿에서는 '/products'에 'post'로 보내고, edit에서는 /products/#{@product.id}에 'put'으로 알아서 데이터를 보내게 되어 있다. 그런데, RightAws::ActiveSdb::Base를 상속할 때는 edit와 delete url의 #{@product.id} 부분이 채워지지 않아서 잘못된 경로로 커밋이 되었는데, RightAws에서 to_s 메서드를 잘못 정의했기 때문(값이 할당되지 않은 변수 @id를 반환하도록 되어 있다)이었다. to_s를 다시 정의하니 제대로 된 경로를 가리키게 되었다.

또 다른 문제는 form_for가 내부적으로 해당 객체의 접근자 메서드를 이용한다는 점인데 RIghtAws는 접근자 메서드 대신 어트리뷰트 형태의 메서드만을 제공한다. 즉 <%= f.text_field :title %> 필드가 있으면 form_for는 자동으로 @product.title = params[:product][:title]를 시도하는데, title이라는 인스턴스 메서드가 정의되어 있지 않으니 오류가 생긴다. 뷰 코드 수정 없이 이를 빨리 해결하려면 각각의 어트리뷰트에 대해 접근자 메서드를 만들어주면 된다.

def title
  self[:title]
end

def title=(value)
   self[:title] = value
end

RightAws를 상속하는 모든 모델에서 모든 어트리뷰트에 대해 이렇게 선언해주는 것은 DRY하지 않으니 ActiveItem에서는 method_missing과 class_eval을 이용해 메서드를 자동으로 생성해주도록 하였다.

def method_missing(method_id, *args)
  method_name = method_id.to_s.match(/\w+/)[0]
  if !self.method_generated
    self.class.attributes.each do |attr|
      method_body = <<-EOV
        def #{attr}
          self[:#{attr}]
        end

        def #{attr}=(value)
          self[:#{attr}] = value
        end
      EOV
      self.class.class_eval(method_body, __FILE__, __LINE__)
    end
    self.method_generated = true
  end

  if self.class.attributes.include?(method_name)
    send(method_id, *args)
  else
    super
  end
end


잘 동작하긴 하는데, method_generated = true/false 부분이 제대로 기능하고 있는지에 대해 확신이 없다. 메서드가 한 번 생성되었으면 다시 생성하지 않도록 하기 위함인데 내가 모르는 사이 동일한 메서드가 클래스에 수십개씩 더해지고 있는게 아닌가 걱정되기도 한다.

find 류의 메서드를 Override한 이유는 reload 때문이다. RightAws에서 find문으로 객체를 받아오면 바로 사용할 수 없고 reload를 해야 한다. find문은 id값과 new_record 여부만 담아오고, reload를 해야 어트리뷰트 목록과 그 값들을 메모리로 가져온다.


@product = Product.find(:first)
@product.title #=> nil
@product.reload
@product.title #=> "스프링노트"

RightAws가 find에서 reload 처리를 해주지 않는 이유는 아마도 필요한 경우만 reload를 할 수 있도록 선택권을 준 것이겠지만(전송량이 곧 비용이기 때문에), reload하지 않은 객체를 사용해야 할 일이 뭐가 있을까 언듯 떠오르지 않아서 ActiveItem에서는 find된 모든 객체에 reload 처리를 해주기로 했다. 이제 컨트롤러와 뷰 코드를 거의 수정하지 않아도 ActiveRecord::Base를 ActiveItem::Base로 바꿔줄 수 있게 되었다!

라고 끝내면 좋겠지만 ActiveRecord는 수천라인으로 된 대형 라이브러리이다. Vaildation과 Association은 시작도 안 했다. 변화를 시도하면, 그 동안 아무런 고민 없이 사용했던 메서드들 객체들이 하나 둘 문제를 일으킬 것이다. 파업이라도 하는 것처럼.

SimpleDB를 사용하기 위해 ActiveRecord를 포기해야 하는가. 포기하지 않아도 되도록 ActiveRecord를 그대로 사용하되 SimpleDB와의 연결을 대신해줄 어답터가 빨리 나와주었으면 좋겠다. SimpleDB 덕분에 ActiveRecord 공부를 더 많이 하게 된 하루였다.

TRACKBACK :: http://flyingmate.net/trackback/46

댓글을 달아 주세요

  1. 트위니  댓글주소  수정/삭제  댓글쓰기

    레일즈 공부 열심이시군요. Core까지 열어보시다니 내공이...

    저도 요새 레드마인 이라는 넘 때문에 심심치않게 레일즈를 보고 있습니다.

    레드마인을 사내 PMS로 도입했는데 오픈소스다보니 고쳐달라는 것도 있고 고치고 싶은것도 있고...

    안부 겸 발자취 남깁니다 ^^;

    2008/04/23 11:01
    • FlyingMate  댓글주소  수정/삭제

      트위니님 안녕하세요. 애자일 OST에서 뵈었던 게 벌써 1년이 다 되어 가네요. 그리고 벌써 3차 CBT를 끝내셨네요^^ 포지션도 바뀌셨나봐요. 전부 축하드립니다!

      레드마인은 거의 엔터프라이즈급 레일스 오픈소스죠. 저도 꼭 뜯어봐야 할 소스코드 목록에 레드마인을 넣어두고 있습니다. 나중에 노하우 좀 전수해 주세요^^

      2008/04/23 15:42

짓궂은 이야기

소소하지 않은 일상 2008/02/24 22:26 by FlyingMate

최근에 개발 관련 글을 많이 적었다. 이 블로그 독자분들 중에서 개발 관련 글을 이해하지 못하는 분도 계실 것이다. 좀 짓궂긴 하지만 의도적인 구석도 있다. 내가 쓰는 글 중 꽤 많은 글들은, 몇 주 전 또는 며칠 전의 나 였다면 쓰지 못했을 글들이다. 즉 2월 첫째주의  FlyingMate였다면 전혀 이해하지 못했을 내용의 글들을 2월 셋째주의 FlyingMate는 마구 아는 채 하며 다루고 있다는 얘기다.

작년 3월부터 css책을 보기 시작했다. 3월 이후에 내가 어느 순서로 어느 분야의 책을 봤는지는 출판사와 주고 받은 메일을 통해 확인할 수 있다. 책을 보면서 발견한 오타를 정리해두었다가 메일로 보내거나 출판사 홈페이지에 오타 등록을 했고, 담당하시는 분이 검토 및 반영 내역을 메일로 답해주시는데 그 메일들을 보관하고 있었다.

발견한 오타를 꼬박꼬박 정리해서, 마치 마감 시한에 쫒기듯 부지런히 전송한 이유는 밝은 세상을 위해서도 아니고 다른 독자들의 편의를 위해서도 아닌 지극히 개인적인 계산 때문이다. 학습 진도를 체크해줄 사람이 필요했기 때문. 내가 얼마만큼 공부하고 있는지 누군가가 알고 있다는 사실만으로도 오늘 이 책을 공부해야 할 이유가 되더라.

물론 스타트업이라는 명확한 목표가 있었고 그것이 가장 큰 이유였지만 그건 매우 거시적이었고, 단계적이고 미시적이면서 심리적인 감시가 필요했었다. 출판사 담당자 분들은 나를 감시할 의도가 전혀 없었지만 감시란 것이 원래 받는 쪽의 느낌.

당시의 나에겐 프로그래밍을 한다는 것이, 성공할지 실패할지 알 수 없는 하나의 도전이였고, html 이외에는 만져본 적이 없는 내가 정말 스스로 웹개발을 해낼 수 있을지  어떻게 될지 나 스스로도 알 수 없으니 주변 사람들에게 같이하자고 권하기도 애매했었다. 좌절하던 시기였다. 물론 일시적인.

4월엔 황대산님의 레일스 입문서로 html 코딩이 아닌, 프로그래밍 언어라는 걸 처음 접하기 시작했고, 7월엔 에이콘 출판사에서 보내준 DOM 스크립트라는 책으로 자바스크립트를 처음 배우기 시작했다. 그 이후로 여러 권의 책을 봤고 꽤 많은 온라인 텍스트와 소스코드를 읽고 작성했다. 개발을 집중적으로 공부한 시기이지만, IT에 발을 담근 후 가장 많은 웹 서비스의 인터페이스와 비즈니스 구조를 벤치마킹한 시기이기도 하다.

처음엔 입문서에 의존했고, 중간과정으로 직접 만든 애플리케이션으로 서비스를 해봤으며 다시 활용서와 해외 블로그의 튜토리얼들에 잠시 기대었다가 지금은 오픈소스와 레일스 프레임워크의 코드를 직접 보면서 필요한 것만 검색하는 방식으로 학습하고 있다.

소스코드를 통해 학습하는 이야기를 좀더 해보면, 처음부터 너무 무거운 애플리케이션의 소스코드를 보다가 이해도 안 되고 지치기도 해서 다른 경량의 애플리케이션을 보면서 고개를 끄덕이고 그 지식을 바탕으로 다시 그 무거운 애플리케이션을 보면서 깨달음을 얻기도 했다. 소스코드를 똑같이 따라 타이핑하면서, 그것에 대한 이해나 의문점을 주석으로 정리하는 방식으로 자바스크립트와 레일스를 공부하고 있다.

처음 보는 코드는 30% 밖에 이해가 되지 않고, 같은 코드를 두 번째 받아 칠 때는 거의 이해하게 되며, 세 번째 따라 치면 외우겠다 싶을 정도가 된다(물론 과장해서).
이 과정은 내가 책을 읽는 방식과 완전히 동일하다고 볼 수 있다. 이제는 처음 보는 코드를 눈으로만 훑어도 어느 정도 이해할 수 있다. 물론 아직 배우는 단계이기 때문에 이런 과정을 지속해야겠지만 지금은 내가 정말 할 수 있을까에 대한 불안감도 없고 앞으로 어떻게 할 것이고 내가 어디까지 해낼 수 있을지 점점 명확해짐을 느낀다.

프로그래밍의 시작을 루비온레일스와 함께한 경우, 즉 유일하게 루비온레일스로만 프로그래밍을 해본 경우는 독특한 경험으로 분류된다. 레일스는 원래 개발자이던 사람들이 대안으로 선택하거나 쉼터로 여기는 분위기이지 컴퓨터 사이언스 전공자나 개발자 지망생이 첫 경험으로 선택하는 기술은 아니다. 미국에서는 대학에서 레일스를 가르치기도 하지만 우리나라는 레일스 프로젝트를 다루는 기업이 희귀하기 때문에 대학교육까지 바라는 것은 욕심이다.

그런데 내가 경험해서 증명한 바로는 레일스는 좋은 프로그래밍 입문 과정이다. (사실  '가장 좋은' 이라는 수식어를 붙였다가, 내가 경험해 본 것이 레일스밖에 없다는 것을 깨닫고 지웠다) 레일스를 만져본 다음에 디자인 패턴 책 보고 데이터 구조 책 보면 훨씬 이해하기 쉽다. 웹 애플리케이션을 만들어 배포해보는 데까지 이르는 거리도 짧다. 난 컴퓨터 과학부를 나름 자부심으로 내세우는 Y대에서 컴퓨터 과학을 2학년까지 배웠지만 레일스와 자바스크립트 가지고 몇 개월 공부한 것이 학습 효과가 훨씬 컸다.

작년 봄, 나에게 레일스를 너무 쉽게 가르쳐주신 황대산님을 직접 만나뵈어 이야기를 나누고 저자 싸인도 받았다. 황대산님의 책을 보면서, 레일스는 진정 개발의 대중화를 가져다 줄 것 같다고 생각했고 그 이야기를 대산님께 했더니 맞장구를 쳐 주셨다. 개발의 대중화라는 말은 기존에 디자이너였건 기획자였건 아무 것도 아니었건 개발을 배우려는 의지, 내 서비스를 만들어보고자 하는 의지만 있으면 누구나 쉽게 개발 능력을 갖게 해준다는 의미였다.

레일스는 정말 그럴 수 있다. 그런데 대산님의 책이 나온지 1년 가까이 지나가는데 여전히 레일스는, 원래 개발자라는 직업을 갖고 있던 사람들의 대안 기술로 남아 있다. 난 주변의 디자이너, 기획자, 또는 그냥 경영학 전공하는 친구에게도 레일스 책을 추천한다. 더불어 css책도. (이통사에 기획직으로 입사한 친구에겐 자바책을 추천했다)

내가 느끼기에, 새로운 서비스에 대한 열망은 개발자들이나 공학도들 보다는 기획자들 또는 경영학도들 사이에서 더 뜨겁다. 그들에게 총을 쥐어주는 것이다. 말로 떠들지 말고 직접 만들어보라는 것이다. 좋은 책 좋은 튜토리얼이 얼마나 많은데. 우리나라 스타트업의 부재는 개발능력을 갖춘 기획자의 부재가 원인일지 모른다.

이 블로그에 방문하시는 분들 가운데 프로그래밍을 전혀 모르는 분들은 이 책 한 권 장만하시기 바란다. 물론 이 책은 아주 기본적인 시작점이고 후속 전략을 지속적으로 세워야 한다. 프로그래밍이라는 것은 이전에 배워왔던 심리학이나 마케팅, 경영학, 수학, 인터페이스 기획, 프로젝트 관리 같은 것들과는 완전히 다른 사고방식, 다른 어휘체계를 갖기 때문에 깊이 들어갈수록 일시적으로 좌절할 수 있다. 하지만 이런 생소한 분야를 조금씩 이해하게 되면서, 학습에 대한 자신감이 생길 것이다.

그래서 나와 같은 호흡을 느끼며 개발 능력을 키워 나가고, 내가 요 며칠 써왔던 알 수 없는 글들이 점점 읽혀지면서, 참 별 것 아닌 내용이었다는 것을 깨닫게 되셨으면 좋겠다. 평생 기획자로 먹고 살 생각이었는데 이 블로그를 보고 개발
시작했다가 결국 사업에 손을 댔다는 이야기를 농담처럼 나눌 수 있게 되기를 바란다.

TRACKBACK :: http://flyingmate.net/trackback/42

댓글을 달아 주세요

  1. Elegant Universe  댓글주소  수정/삭제  댓글쓰기

    좋은 글. 항상 잘 읽고 있습니다. 저 역시 같은 생각으로 프로그래밍 쪽에 본격적으로 손을 댄지 대략 3달 가까이 되었습니다. 처음 어느 애플리케이션의 코드를 보며, '언제쯤 나도 이런 것을 자유자재로 구현할 수 있는 실력을 갖추게 될까?' 라는 조급한 마음을 가지고 정신없이 달려와서 지금에 이르렀는데, 많은 능력과 자신감을 갖추게 되었습니다. 물론, 앞으로 알아야 할 것들이 매우 많지만,,, 저와 같은 길을 가고 있는 것 같아 자주 들려서 좋은 글 읽고 있습니다. 앞으로도 좋은 교훈 들려주시길 바랍니다.^^

    2008/02/25 03:43
    • FlyingMate  댓글주소  수정/삭제

      예전에 덧글을 남겨주셨을 때, Elegant Universe에 대해 검색해 봤더니, 초끈이론 등을 다룬 물리학 서적이더군요. 물리학과 천문학도 공부해보고 싶은 분야인데, 깊이 들어갈수록 철학적인 향기가 나서 매력적인 것 같습니다.

      비슷한 비전을 보고 나아가고 있다면 언젠간 분명 만나뵙게 될 날이 올 거라 생각합니다. 공감의 덧글 남겨주셔서 감사드리고, 제 스스로가 성공하기를 바라는 만큼, Elegant Universe님의 성공도 기원해드리겠습니다.

      2008/02/25 19:46
  2. HJazz  댓글주소  수정/삭제  댓글쓰기

    컥...대단하시군요...
    근데 옛날에 언젠가 Prototype.js 소스코드를 직접 분석하신다는 얘기도 들었던 것 같은데 ^^;

    2008/05/19 01:18
    • FlyingMate  댓글주소  수정/삭제

      분석이라고 하면 너무 거창하고
      prototype.js의 코드를 읽고 모방해보면서
      자바스크립트 자체에 대해 이해하기도 하고
      객체 지향 자바스크립트에 익숙해지기도 하고 그랬죠^^;;
      그 때 한창 자바스크립트 공부하던 때였네요.
      지금은 다행히 많이 익숙해졋습니다~

      2008/05/19 20:11

Rails Caching

소소하지 않은 일상 2008/02/24 16:42 by FlyingMate

Rails Caching

레일스의 캐싱은 (다른 언어의 프레임워크들은 어떻게 하는지 모르지만) 굉장히 친절해서, 컨트롤러에 코드 몇 줄 추가하는 것 만으로도 상당한 수준의 캐싱을 구현할 수 있다. 레일스 캐싱에 대해 정리된 메뉴얼(레일스 관련 서적, 온라인 튜토리얼)은 대게 사용법만을 다루기 때문에, 캐싱 메커니즘을 이해하는 데는 소스코드를 함께 보는 것이 도움이 된다. 소스코드는 해석이 아닌 진실 그 자체이니깐.


캐싱은 액션 컨트롤러(actionpack/action_controller/caching.rb)에 구현되어 있다. 우선 기본적으로 Page Caching, Action Caching, Fragment Caching 세 가지 캐싱이 가능하고 각각은 Caching모듈의 하위 모듈 Pages, Actions, Fragments로 분리되어 있다.

action_controller.rb에서 load path 배열을 담는 전역변수 $:에 action_controller 디렉토리를 추가(unshift)하고, ActiveSupport의 라이브러리들을 사용할 수 있는 상태로 만든다($:.unshift 또는 gem 'active_support'). 그 다음 확장 모듈이 담긴 파일을 먼저 require하고 ActionController.Base.class_eval 블럭에서 그 모듈들을 include한다.

# action_controller.rb
# ...
require 'action_controller/cgi_process'

require 'action_controller/caching'
require 'action_controller/verification'
# ...

ActionController::Base.class_eval do
  # ...
  include ActionController::Cookies
  include ActionController::Caching
  include ActionController::Verification
  # ...
end

ActionController::Caching 도 그 모듈 중 하나로서 include되는데, self.included(base) 메서드 내의 base.class_eval 블럭에서 Pages, Actions, Fragments 모듈을 또 include한다. (self.included에 대해서는 저번 포스트에서 미리 다루었다) 레일스 2.0 이전에는 class_eval내에서 include를 직접 실행하지 않고 base.send(:include, Pages, ...) 이런 식으로 메서드 심벌과 모듈 상수명을 send에 넘겼었다.

# action_controller/caching.rb
# ...
  module Caching
    def self.included(base) #:nodoc:
      base.class_eval do
        include Pages, Actions, Fragments

        if defined? ActiveRecord
          include Sweeping, SqlCache
        end

        @@perform_caching = true
        cattr_accessor :perform_caching
      end
    end
    (..중략..)
  end


프레임워크가 성숙해져 간다는 느낌이 드는 부분은 새로 추가된 if문에서 발견할 수 있는데 ActiveRecord가 정의되어 있어야지만 Sweeping과 SqlCache를 include한다. Sweeping모듈 내의 Sweeper 클래스는 ActiveRecord의 Observer를 상속하는데, 기존에는 ActiveRecord가 당연히 require/include되었을 것으로 가정하고 동작했지만 이제는 DataMapper 등의 다른 모듈을 사용하거나 DB관련 모듈을 사용하지 않는 경우를 고려한 if defined?(ActiveRecord) 구문이 추가되었다.

SqlCache는 새롭게 추가된 캐싱 모듈인데, ActionController::Base의 perform_action 메서드를 ActiveRecord::Base.cache 블럭으로 감싼다. perform_action은 이름에서 알 수 있듯이 레일스 애플리케이션으로 요청된 액션을 수행하는 레일스의 가장 기본적이고 중요한 메서드이다.

요청받은 액션 이름이 존재하지 않을 경우 method_missing으로 전달하는 처리도 한다. 즉, SqlCache가 레일스로 들어오는 모든 요청을 통제하면서 캐싱 처리를 한다는 의미로 볼 수 있다. SqlCache 모듈이 등장하면서 ActionController보다는 ActiveRecord측에 캐싱 관련 코드가 많이 추가되었다. 나중에 자세히 살펴봐야겠다.


Page Caching

대표적인 세 가지 Rails Caching 중 Page Caching의 성능이 가장 월등할 수밖에 없는데 한 번 렌더링된 뷰를 html로 만들어 public폴더에 담고, 해당 url로 get 요청이 있을 때 컨트롤러가 직접 응답하지 않고 만들어져 있는 html을 전송한다. 이 과정에서 상태 코드가 200인지도 체크한다. 페이지 캐싱이 레일스가 응답하는 것보다 100배나(정말?) 빠르다고 주석에 설명하고 있다.

expire_page와 cache_page, caches_page를 정의한다. 이 메서드들은 클래스 변수인 @@perform_caching가 true인지 false인지를 매번 확인하는데(return unless perform_caching), 자바스크립트에는 흔하게 보이는 형태이지만 루비에서 만큼은  DRY스럽지 않다고 느껴지는데 어쩔 수 없어보인다.

cache_page는 path를 인자로 받고, caches_page는 액션이름의 심벌을 인자로 받는데 내부적인 처리는 동일하다. (caches_page는 인자로 받은 액션 각각에 대해 cache_page를 호출할 뿐이다) html(디폴트 확장자로, 변경 가능)파일을 만드는 것(File.open)이 cache_page의 역할이고, 그 파일을 지우는 것(File.delete)이 expire_page의 역할이다.

이 동작들은 benchmark 블럭 내에서 이루어지는데, ActionController의 Benchmarking은
루비의 Benchmark를 확장해서 benchmark 메서드에 log_level과 use_silence 옵션을 추가하는 등의 구현을 담고 있다. 거의 모든 캐싱 동작들이 benchmark 블럭 내에서 이루어지기 때문에 캐싱 성능 측정이 용이하다.

caches_page는 show, create 액션 등 페이지 뷰가 발생하거나 페이지가 최초로 생성되는 액션을 컨트롤러 차원에서 선언해주면 되고, cache_page는 조건부 처리가 필요할 때 액션 내에서 호출하는 용도, 그리고 expire_page는 페이지가 변경되는 시점인 update 등의 액션 내에서 호출하면 된다.


Sweeper

expire_page를 직접 호출하지 않고 좀더 엘레강스하게 자동으로 처리하고 싶다면 sweeper(청소부)를 만들면 된다. sweeper는 ActiveRecord::Base의 자식인 model들이 서식하는 models 폴더에 놓이지만 ActionController::Caching::Sweeper를 상속하는 미운오리X끼인데, 이복 형제
(모델)들을 감시(observe)하고 있다가 그들에게 변화가 생기면(save, create, update) 적당한 after_xxx 메서드를 실행한다.

이 메서드 내에서 expire 처리(FileUtils.rm_rf 등)를 적어주면 되고 Page 캐싱 뿐만 아니라 Action, Fragment 캐싱도 잘 처리(expire_fragment)한다. 아래 코드는 블로그 애플리케이션 Typo의 BlogSweeper의 대략적인 형태이다.

class BlogSweeper < ActionController::Caching::Sweeper
  observe Category, Blog, Sidebar, User, Article # , ...
  
    # ...

  def after_save(record)
    # ...
  end

  def after_destroy(record)
    # ...
  end

    # ...
end

사용하려면 컨트롤러에서 cache_sweeper를 호출해주면 된다.

cache_sweeper :blog_sweeper


Action Caching

페이지 캐싱은 게시물이 모든 사람에게 동일하게 보여질 때만 사용할 수 있다. public폴더는 누구나 접근할 수 있기 때문에 인증이 필요하거나 사용자별로 다른 데이터가 제공되어야 하는 페이지는 페이지 캐싱을 이용할 수 없다. 대신 Action Caching이나 Fragment Caching을 이용해서 before_filter가 인증처리를 한 다음 캐시 파일을 사용자에게 전송해야 하며, 그 파일은 공개되지 않은 곳에 저장되어야 한다.

Action Caching과 Fragment Caching은 기술적으로 동일하며 사용 방식에만 차이가 있다. ActionController::Caching::Actions 모듈은 ActionCacheFilter와 ActionCachePath라는 하위 클래스를 가지고 있고, 모듈 내에서 두 클래스를 사용한다.  ActionCacheFilter.new가 어떻게 사용되는지 보자.


module ClassMethods
  def caches_action(*actions)
    return unless perform_caching
    around_filter(ActionCacheFilter.new(*actions))
  end
end

컨트롤러에서 caches_action을 선언하면, 인자로 받은 액션의 목록을 ActionCacheFilter의 생성자로 넘긴다. ActionCacheFilter 클래스는 이렇게 생겼다.

class ActionCacheFilter
  def initialize(*actions, &block)
    @options = actions.extract_options!
    @actions = Set.new actions
  end

  def before(controller)
    return unless /
      @actions.include?(controller.action_name.intern)
    # ..
  end
 
  def after(controller)
    return unless /
      @actions.include?(controller.action_name.intern)
    # ..
  end

    # ..
end

around_filter는 개념적으로 before_filter와 after_filter를 합친 것이다. around_filter의 인자로 필터 메서드 이름(심벌), 블럭, 필터 클래스의 이름, 필터 클래스의 인스턴스 등 총 4가지를 넘길 수 있다. 필터 메서드의 이름을 넘기는 경우, 해당 필터 메서드는 yield나 block.call을 포함해야 하며 컨트롤러 내의 액션들은 그 안(yield, block.call)에서 처리된다.

around_filter에 블럭을 인자로 넘길 경우 { |controller, action| .. } 이렇게 블럭 지역변수로 controller와 action을 사용해야 한다. 필터 클래스를 사용하는 경우 해당 클래스 내에 after와 before 두 개의 메서드를 정의하거나, 그 대신 filter 메서드 하나를 정의하면 되는데, 필터 클래스의 이름을 넘기는 경우는 이들 메서드를 클래스 메서드로 정의해야 하고, 필터 클래스의 인스턴스를 넘기는 경우는 이들 메서드를 인스턴스 메서드로 정의해야 한다는 차이점이 있다.

ActionCacheFilter 내에서는 before과 after를 정의하고 있으며 @actions에 필터링할 액션 목록을 따로 담아 관리한다. 즉 filter를 컨트롤러에서 사용할 때 :except나 :only 옵션이 했던 일을 @actions 변수가 대신 관리하면서 before과 after 메서드 내에서 @after.include?를 통해 캐싱할 액션들을 필터링하는 것이다.

실질적인 Action 캐싱 과정은 이 ActionCacheFilter 내에서 일어나는데, 자체 캐싱 메커니즘을 따로 갖고 있지 않고 Fragment 캐싱을 사용한다. 즉 Fragments 모듈의 read_fragment와 write_fregment를 이용해 Action 캐싱을 하고 만료를 시킨다. ActionCachePath는 캐싱 파일의 이름과 경로를 관리하는데, 실제 애플리케이션의 url(url_for 헬퍼를 이용해)을 기준으로 그것을 결정한다.

Typo의 경우 플러그인을 통해 추가된 caches_action_with_params 메서드를 사용하고, 이 메서드는 url에 parameter가 붙어있는 페이지까지 캐싱을 한다. #{RAILS_ROOT}/tmp/cache 폴더에 #{hostname}/#{controller.controller_name}/#{controller.action_name}/#{param_string}.cache의 형태로 만들어지는데, 파일을 열어보면 페이지 캐시된 html 파일과 형태가 유사하다.


Fragment Caching

Action Caching의 실질적인 구현은 Fragment Caching을 이용한다. Page Caching은 html 파일을 만들어 public 폴더에 담아두기만 하면 되었다. 즉 파일시스템을 이용하기 때문에 고민할 부분이 많지 않았던 반면 Fragment Caching은 저장 방식에 대해 기본적으로 4가지 선택사항(FileStore, MemoryStore, DRbStore, MemCacheStore)이 주어지고 커스터마이징된 cache_store를 사용할 수도 있다.

MemoryStore는 가장 간단하게 인스턴스 변수(Hash)에 캐시를 저장한다. 인스턴스 변수는 메모리 상에 존재하니 그것을 이용하는 것이다. ActionController::Base의 클래스 변수인 @@allow_concurrency(동시성 허용)가 true일 경우, 약간의 추가작업을 해주는데, 캐시의 읽기/쓰기/삭제 과정을 Mutex 인스턴스(@mutex = Mutex.new)의 synchronize 블럭으로 감싼다. 이 부분을 모듈화해서 ThreadSafety(쓰레드 안전성) 모듈에 담고 MemoryStore.module_eval { include ThreadSafety } 형태로 추가한다.

Mutex는 루비의 Thread 라이브러리에 정의되어 있고, 어느 특정 쓰레드에 자원 사용의 독점권을 부여하는 데 쓰인다. 즉 여러 개의 요청이 동시에 들어와 MemoryStore가 사용하는 인스턴스 변수 내에서 데이터가 섞이거나 잘못 할당되는 것을 방지하기 위해, 요청들을 일단 줄세우고 하나의 요청 쓰레드가 끝나야(lock) 다음 쓰레드를 실행시키는(unlock) 등의 관리를 해준다.

  def synchronize
lock
begin
yield
ensure
unlock
end
end

DRbStore와 MemCacheStore는 이런 MemoryStore를 상속하기 때문에 쓰레드 안전성을 보장받는다고 할 수 있다. DRb에 대해서는 ChadFowler의 Intro to DRb가 좋은 출발점. 다음은 Fragments 모듈 내의 DRbStore 클래스 구현부분이다.

class DRbStore < MemoryStore
  attr_reader :address

  def initialize(address = 'druby://localhost:9192')
    super()
    @address = address
    @data = DRbObject.new(nil, address)
  end
end

DRb는 클라이언트와 서버 둘 다 실행해야 하고, 일단 실행된 후에는 대칭적으로 동작하는데, caching.rb 내에서는 클라이언트 역할만 정의하고 있으므로 DRb 서버측도 구동(DRb.start_service)해줘야 할 것으로 보인다. 이 부분은 좀더 공부해야겠다. 아마존의 simple_db가 이런 개념(객체 공유하기)이 아닐까 추측해보고 있다.

MemCacheStore는 그 유명한 Memcached 메커니즘을 사용하며, 레일스 내에 포함되어 있지 않기 때문에 gem이나 library 설치가 필요하고 이를 require한다. MemCache 부분도 아직 경험해볼 일이 없어서 깊이 이해하지 못했다. DRb와 방식은 유사(루비 객체 주고받기)하되 여러 장소에 분산시켜 공유하는 모델인 듯 하다. 시간날 때 memcache-client gem의 memcache.rb와 루비의 drb.rb를 꼭 뜯어봐야겠다고 벼르고 있다.


Caching in Java

자바개발자를 위한 레일스에서는 캐싱에 대해, 자바에서는 모델을 캐싱하고 레일스에서는 뷰를 캐싱하며, 자바에는 뷰를 캐싱하는 데 널리 쓰는 메커니즘이 딱히 존재하지 않는다고 언급하고 있다. 새로 추가된 SqlCache에 의해 레일스의 모델 캐싱 부분이 좀더 강화되지 않았나 싶다. 그리고 자바에서는 뷰를 전혀 캐싱하지 않는지, 만약 자바에서도 뷰 캐싱이 있다면 어떤 방식으로 처리하는지 궁금하다.

TRACKBACK :: http://flyingmate.net/trackback/41

댓글을 달아 주세요


레일스 캐싱에 대한 글을 쓰려다가 글이 자꾸 삼천포로 빠지길래 아예 삼천포를 주제로 다시 글을 정리하고 있다. 이 다음 글로 레일스 캐싱을 적어봐야 겠다. 아래 코드는 레일스 캐싱의 구현부분이지만 이 글은 캐싱이 아니라 레일스의('루비의'가 아닌) 클래스 확장 방식을 다루려고 한다. 레일스에서 Action Caching을 구현하는 Actions 모듈은 아래와 같이 시작한다.

module Actions
  def self.included(base)
    base.extend(ClassMethods)
    base.class_eval do
      attr_accessor :rendered_action_cache, /
        :action_cache_path
      alias_method_chain :protected_instance_variables, /
        :action_caching
    end
  end
     
  module ClassMethods
    def caches_action(*actions)
      return unless perform_caching
      around_filter(ActionCacheFilter.new(*actions))
    end      
  end

    (..중략..)
end

self.included(base), base.extend, base.class_eval은 레일스의 모듈 내에서 흔하게 볼 수 있다. self.included(base)를 통해, 액션 컨트롤러(base)가 현재 모듈을 include했을 때의 초기화 부분을 정의하고 있다. base라는 이름의 인자로 넘겨받은 ActionController::Base에 ClassMethods에 담긴 클래스 메서드들을 포함시키고(extends), 인스턴스 변수에 대한 접근자(accessor)와 alias_method_chain 선언을 컨트롤러 내에 추가한다(class_eval).

alias_method_chain는 논란이 많은 메서드인데, 레일스 내에서 메서드 확장을 위해 빈번하게 사용된다. 기존 메서드를 override할 때, override를 정의하는 구현부 내에서 override하기 이전의 기능을 사용하려 할 때 호출한다. 기존에는 두 줄의 alias_method를 통해 아래와 같이 선언을 했다.

alias_method :old_good_method, :good_method
alias_method :good_method, :new_good_method

good_method라는 이름이었던 이전의 메서드(superclass의 메서드 또는 class_eval 등으로 재정의되기 전의 메서드)에 임시적으로 다른 이름을 부여하고(예를 들어 old 접두어 같은), override를 정의할 때의 메서드 이름 역시 새로운 이름을 부여해서(역시 예를 들면 new 접두어 같은) 메서드의 확장을 좀더 명시적으로 드러내는 것이다.

이것을 하나의 메서드로 통합한 것이 alias_method_chain인데, 이 메서드는 active_support/core_ext/module/aliasing.rb에 구현되어 있다. 위에 있는 두 줄의 alias_method를 한 줄의 alias_method_chain으로 선언한 예는 다음과 같다.

alias_method_chain :good_method, :new_feature

이 선언은 액티브 서포트의 메타 프로그래밍에 의해 다음과 같이 변환된다.

alias_method :good_method_without_new_feature, :good_method
alias_method :good_method, :good_method_with_new_feature

이렇게 선언한 후, 기존의 good_method에 override할 내용을 good_method_with_new_feature 메서드에 구현한다. good_method_without_new_feature를 그 내부에서 사용할 수 있다.

def good_method_with_new_feature
  good_method_without_new_feature + "new feature"
end

alias_method_chain 구현부의 변천사를 살펴보면, 처음(active_support-1.3.1)에는 스키니했지만 점차 살을 붙여가고 있다. 1.4대에서
?, !, =로 끝나는 메서드와 블럭 처리가 추가되었고, 2.0.2인 현재 메서드 접근 제어(public/private/protected)까지 처리하고 있다.

def alias_method_chain(target, feature)
alias_method "#{target}_without_#{feature}", target
alias_method target, "#{target}_with_#{feature}"
end

caching을 비롯해 레일스의 라이브러리들 상당수가 class_eval을 통해 base 클래스를 다시 열고 그 안에 alias_method_chain을 두거나 추가적인 메서드를 정의하는 형태로, 또 클래스 메서드는 ClassMethods라는 모듈에 따로 모아 extends하는 형태로 구현되어 있다.

이를 통해 기존의 클래스를 변형하지 않고 모듈화된 라이브러리의 변수와 메서드를 덮어 씌우는(wrapping) 형태로 기능을 확장하는데, 여기서 Decorator 패턴과 AOP(Aspect Oriented Programming)의 특징을 발견할 수 있다고 한다.

자바에서의 Decorator패턴은 Wrapping할 대상 클래스의 superclass인 추상 클래스를 Decorator가 상속해서 메서드를 재정의하되, 그 정의 내에서 기존의 메서드를 호출하는 부분이 중요한데, 레일스에서 class_eval과 alias_method_chain이 했던 역할(기존에는 두 줄의 alias_method가 했던)이 이것과 유사하다고 볼 수 있다.

deepblue님이 소개해주신 The Rails Way에서도 alias_method_chain을 패턴 차원에서 다루고 있는데, 정적 언어에 주어진 제약들 때문에 이런 저런 재미있는 방법(패턴)으로 해결했던 과제들을 동적 언어에서는 너무 싱겁게 풀어버려서 패턴이라는 고상한 단어를 붙이기가 무색해져 버렸다. 두 세 줄 짜리 코드에 패턴이 웬 말인가.

기존의 코딩 습관들에서 좋은 것들을 추출해 패턴으로 만들고, 상황에 따라 패턴을 선택해 코드로 구체화하는 자바와는 반대로 루비는 이미 만들어진 코드에서 패턴의 흔적(?)들을 추출해낼 수 있다. 꿈보다 해몽. 어쩌면 리팩토링이나 코딩 스타일 가이드 조차도 다른 언어에 비해 루비에서 만큼은 큰 의미가 없을지도 모르겠다는 생각이 든다. 이건 좀더 겪어봐야겠다. 레일스의 개발자들 역시 패턴을 고려해 설계하겠지만 기존의 정적 언어에서는 상식이었던 개념들로부터 독립적으로 사고해야 할 것 같다.

위의 Action Caching의 코드로 돌아와서, class_eval에서 정의하고 있는 alias_method_chain은 ActionController의 Base클래스에 정의되어 있는 protected_instance_variables를 확장한다. 기존의 메서드는 특별한 인스턴스 변수 이름들의 목록을 배열로 반환하는데, 재정의된 메서드는 이 배열에 변수 이름 하나를 추가한다.

이 모듈이 include되면 메서드가 이전과 조금 다르게 동작하게 되는 것이다. 사실 이 부분(protected_instance_variables_with/without_xxx)이 크게 중요한 것은 아니지만, alias_method_chain의 동작과 사용예를 잘 이해해야 프레임워크의 다른 부분을 이해하는 데 속도를 더 높일 수 있다. alias_method_chain의 정체가 무엇이고 왜 쓰였는지 이해하는 데 나도 좀 헤맸다.

TRACKBACK :: http://flyingmate.net/trackback/40

댓글을 달아 주세요


예약 주문한 자바 개발자를 위한 레일스가 오늘 오전에 도착했다. 험블 프로그래머님의 번역 신공의 결과물로써 발렌타인 데이인 오늘, 초콜릿을 대신하여 많은 남성 개발자들의 가슴을 달콤하게 녹여줄 것으로 기대한다.

난 저자 소개, 저자 서문, 역자 서문, 그리고 추천사까지, 책의 차례보다 먼저 오는 부분을 항상 빠짐없이 읽는다. 대게는 이 책을 왜 읽어야 하는지에 대한 당위성과 이 책이 다루는 기술의 장점 등을 가볍게 요약하고 있어서 가볍게 읽어보면 좋다. 그런데 험블님의 역자 서문은 한 편의 칼럼이라고 생각될 정도로 깊이가 느껴진다.

'읽기 전과 읽은 후의 자신이 다르지 않으면 책을 헛읽은 것과 다름이 없다' 라는 말이 있더군요

이런 인용으로 시작하는 서문은, 지식근로자로서 개발자의 위상과 그들의 지적 능력 향상이 어떤 의미가 있는지를 언급하고, 마지막엔 번역과 반역 사이의 타협점을 어느 지점으로 잡았는지 그래서 이 책이 왜 이런 독특한(?) 형태로 나오게 되었는지에 대한 고백이 담겨있다.

내가 느끼는 지식근로자라는 단어는 정말 무거운 어휘이다. 컴퓨터 앞에서 일하는 사무직 근로자를 통째로 지식근로자라고 부르기엔 많이 아깝다. 타계한 피터드러커는 지식근로자의 정의와 그로 인한 경영 및 경쟁 환경의 변화에 대해 자신의 저서에서 지속적으로 강조하였다. 지식근로자라는 말의 이런 무게감이 험블님의 서문에는 충분히 담겨있는 것 같다.

사용자 삽입 이미지

자바 개발자를 위한 레일스


이 책의 제목은 '자바 개발자를 위한 레일스'이지만, 난 이 책을 '레일스 개발자를 위한 자바'로 읽어야겠다는 생각으로 구매했다. 물론 이 책의 타겟은 자바 개발자이고 그들이 보는 것이 훨씬 유익하리라 생각하지만, 자바를 구경만 해봤고 집중해서 배운 언어는 루비와 레일스밖에 없는 나 같은 경우도 자바와 레일스의 차이점을 보면서 자바를 그리고 레일스를 더 잘 이해할 수 있을 거라 기대했다.

메서드와 객체 정의, 제어문, 그리고 프레임워크의 구석구석에 대해 자바와 루비/레일스가 어떤 차이를 보이는지 코드를 비교해가면서 보여준다. 이 책의 타겟(자바 개발자)들은 루비 쪽을 자세히 볼 것이고, 난 초반에는 자바쪽을 자세히 보았지만 점점 루비/레일스 부분을 자세히 보게되고 자바 쪽은 궁금증을 채우는 정도로 활용하게 되었다. 이런! 루비와 레일스를 이해하기 위해 자바 코드를 같이 보는 것이 정말 도움이 된다. 이 책은 아마도 모든 개발자를 위한 책이다.

게다가 자바 부분을 아예 떼어놓고 루비와 레일스 부분만 보더라도 충분히 가치를 하는 책이다. 물론 그러다보니 다루는 내용 자체에서는 기존에 번역된 루비와 레일스 관련 서적과 부분적으로 겹칠 수 있다. 그렇다 하더라도 다루는 관점 자체가 다르고 겹치는 부분보단 그렇지 않은 부분이 훨씬 많으며 번역 과정에서 삽입된 루비 1.9와 레일스 2.0의 변경점들을 책의 흐름 안의 적합한 지점에서 다루어 주었다는 점도 좋다.

나의 경우는 최근에 계속 레일스의 edge(2.0.2버전)을 다루고 있었고, 조만간에 루비 1.9가 레일스의 베이스로 깔릴 것이기 때문에 1.9에서 주요 객체 메서드들의 동작방식이 어떻게 변하고 어떤 메서드가 추가되는지 적절한 문맥 안에서 설명해주는 것이 유용했고 앞으로도 유용할 것 같다. (아직 책의 1/4 밖에 보지 못했다)

레일스를 다루기 전에 루비와 자바의 차이를 중점적으로 다루는 부분을 100페이지 가량 보다가 마음이 급해져서 험블님이 부록에 추가하신 JRuby 설치와 활용에 대한 글과 deepblue님이 작성하신 REST 중심 개발에 대한 글을 먼저 보게 되었는데, deepblue님은 워낙 리소스 중심 개발을 추구하는 분이시고 그와 관련된 이야기도 많이 해오신지라 내용이 반갑고 익숙했다.

deepblue님이 소스코드 일독을 추천하셨던 Beast의 코드를 살펴보면서, 처음에 login과 signup이 어느 컨트롤러에 있는지 조금 헤맸던 기억이 난다. 그리고 sessions_controller가 뭐하는 물건인지 코드를 보기 전까지는 알지 못했었다. 사상의 충격이랄까, login은 sessions_controller의 create 액션을 향해 라우팅되어 있었고, singup은 users_controller의 create로 라우팅되어 있었다. 물론 logout은 sessions_controller의 destroy로.

이런 구조로 사고하는 것은 충분히 가능하고 충분히 즐겁지만 기우가 조금 생겼는데, 제작자의 사고방식과 사용자의 사고방식이 괴리될 수 있다는 우려이다. signup이나 login이라는 것은 오프라인에서 고객이 계약이나 등록을 하고 본인 확인 절차를 거쳤던 관습을 애플리케이션에서 개념적으로 모사한 것인데, 리소스 중심으로 사고할수록 그런 부분들을 사용자의 사고방식과 다르게 생각해야 한다.

이런 계정관리 뿐만 아니라 애플리케이션 인터페이스의 많은 부분은 오프라인의 관습 또는 습관을 추상적이고 개념적으로 차용해왔고, 그것이 또 온라인상의 관습으로 굳어져 사용자들은 그것에 익숙하기 때문에 이런 불일치가 지속되는 한 개발자는 머릿속에 두 개의 자아를 가지고 있어야 한다. 사용자로서의 자아와 설계자로서의 자아.

음악이나 동영상 플레이어에서 정지, 재생 버튼이 화면에 표시되는 것도 디바이스 버튼들의 모사의 결과물이고 이를 리소스로 생각하면 데이터 스트림을 생성하고 소멸하며 다른 기능 역시 사용자가 버튼을 누를 때의 사고방식과 다르게 설계를 하게 될 것이다. 기획자가 그 동안 중도자적 역할을 해왔지만 사실 그것은 근본적인 해결책은 아니라고 생각하는 1인이기 때문에..


로그인을 세션이라는 리소스 중심으로 처리한 Beast도 login이라는 링크 이름을 사용해 sessions/create로 은밀히 라우팅 처리를 해줘야 했다. 물론 그래서 그게 안 좋다는 이야기는 아니고 개발자가 자신의 다중인격적인 사고방식을 좀더 강화해야겠다는 생각이 들었다. '로그인' 버튼 대신 '세션 생성' 버튼을 달 정도만 아니면 되지 않겠는가. 사실 설계와 사용을 같이 고려해주는 게 그렇게 어려운 일도 아니다. 위에서 말한 것은 역시 기우이고 나 역시 REST를 좋아한다.

험블님을 직접 만나뵌 적은 없지만 블로그에 작성하시는 통찰력 있는 글이나 미투데이에 올리시는 진지한 문체의 문장 하나하나만 봐도, 매일 스스로를 개선해 나가시고 매일 뭔가를 배워나가는 분이라는 느낌이 들었다. 이 책을 읽다가 문득 깨달은 것은 이 책이 경어체로 되어 있다는 사실이었다.

평소에 블로그에도 경어체로 글을 올리시기 때문에 역자 서문의 글이 경어체라는 것도 너무 자연스러워 미처 눈치채지 못했고 그렇게 자연스럽게 책의 끝까지 경어로 되어있었다. 영어는 경어의