Languages/Ruby or Rails2008.10.13 13:39

잘 알려진 이야기입니다만, Rails는 REST를 꽤 우아하게 지원하는 플랫폼입니다. 그리고 Prototype을 사용한 JavaScript 연동 기능도 잘 지원하고 있죠. 그래서 프로그래밍하기에 따라서는 JavaScript를 전혀 할 줄 몰라도 AJAX 프로그래밍을 할 수 있습니다.

그런데 Rails가 제공하는 JavaScript 지원 기능들을 사용해 프로그래밍을 하다 보면, 이런 생각이 듭니다. 본질적으로 JavaScript는 CLIENT쪽 제어를 위한 기능이거든요. 이런 기능들이 서버쪽에서 생성하는 코드에 묻어들어가게 되면, 요즘 한참 뜨고 있는 "무간섭 자바스크립트" 코딩 철학에 맞는 프로그래밍을 하기는 좀 어려워지죠. 거기다, 클라이언트쪽 코드를 서버측 기술에 독립적인 형태로 구현하기도 어려워집니다. "서로 다른 티어(tier) 사이에 존재하는 종속성을 최소화한다"라는 것을 프로그래밍의 대원칙으로 삼는다면, 그 원칙에 맞게 코딩하기가 어려워진다는 겁니다.

그렇다면, 그 원칙을 지키면서 코딩을 하려면 어떻게 해야 하나요? 가급적 HTML과 JavaScript 코드는 서버쪽 스크립팅 기술과는 독립적으로 작성해야 할 겁니다. 그렇게 하면 다음과 같은 부수적 효과를 누릴 수 있습니다.

1. 서버측 기능이 풍부하지 않아도 된다. (REST만 지원해도 상관없다.)
2. 서버측에서 어떤 언어나 스크립팅 태그를 지원하건, 그에 무관한 코드를 작성할 수 있다.
3. 클라이언트 JavaScript 코드를 서버와는 상관없이 간단하게 교체/수정할 수 있다.

웹 서버가 REST만 지원해도 된다는 것은 어떤 의미일까요? RESTful 코딩 패턴만을 사용하여 프로그래밍을 하면 웹 서버를  마치 클라이언트 측 Resource에 대한 Persistency layer로 추상화한 코딩을 할 수 있다는 것에 주목합시다. 물론, 이렇게 하게 되면 MVC 패턴에 있어서 Model, View, Controller 로직이 전부 클라이언트 쪽으로 이동하게 됩니다. 그게 과연 옳은 일인지는 논쟁의 소지가 있으므로 언급하고 싶지는 않습니다만, 최근 jQuery등의 라이브러리 등장과 JavaScript에 관한 좋은 교과서들이 나옴에 따라, 그런 코드를 만들기가 쉬워지긴 했습니다.

예를 들어서 한번 살펴보죠. 여러분이 클라이언트 측에 임의의 Element를 동적으로 생성하고, 그 Element가 만들어질 때 마다 그 절대 위치를 Location이라는 이름의 웹 서버 리소스에 저장하고 싶다고 해 봅시다. 그런 일을 하려면, 우선 rails 서버에 Location이라는 이름의 리소스를 만들어 줘야 합니다.

$~ /work/rails/foo$ script/generate scaffold location elementid:string x:integer y:integer width:integer height:integer

위와 같이 실행하면 location이라는 resource에 대한 scaffold 코드가 쫙 만들어질겁니다. 그 컨트롤러 코드의 일부분을 잠깐 보시면...

class LocationsController < ApplicationController
  ...
  # POST /locations
  # POST /locations.xml
  def create
    @location = Location.new(params[:location])

    respond_to do |format|
      if @location.save
        flash[:notice] = 'Location was successfully created.'
        format.html { redirect_to(@location) }
        format.xml  { render :xml => @location, :status => :created, :location => @location }
        format.json { render :json => {:dbid => @location.id}, :status => :created }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @location.errors,
                             :status => :unprocessable_entity }
        format.json { render :json => @location.errors,
                             :status => :unprocessable_entity }
      end
    end
  end
  ...
end

생성된 코드 그대로는 아니고, 수정을 좀 했습니다. (몇줄 안됩니다. 코드라고 보기엔 좀 그렇고, '설정(configuration)'에 가까운 코드죠. Ruby 프로그래밍을 할 줄 몰라도 짤 수 있는, 그런 수준의 코드입니다.) 본 예제에서는 클라이언트에서 json 형태의 회신을 요구하는 것으로 가정을 했습니다. 서버는 클라이언트에서 POST 형태로 전달된 파라메터를 분석해서 Location 객체를 만들고, 그 객체를 database에 성공적으로 저장하고 나면 역시 json 포멧의 메시지를 ㄹ응답으로 전달해야 합니다. 그래서 format.json {...} 부분의 코드를 넣었구요. 이 코드를 보면, @location.save가 성공할 경우, { 'dbid': 33 } 과 같은 형식의 json 데이터가 클라이언트로 날아가게 됨을 알 수 있습니다.

자. 그러면 이것으로 서버측에서는 클라이언트가 날리는 데이터를 가지고 Location이라는 모델 객체(리소스)를 만들어 database에 저장할 준비를 끝냈습니다. 남은 일은 클라이언트에서 제대로 된 json 형태의 request를 날려주는 것 뿐입니다. 그럼 클라이언트 측 코드를 보시죠. jQuery로 작성한 코드입니다.

var createLocationAjax = function(options) {

  $.ajax( {
      url: options.url,
      type: 'post',
      data: {
        'location[elementid]': options.elementId,
        'location[x]': options.pageX,
        'location[y]': options.pageY,
        'location[width]': this.width,
        'location[height]': this.height
      },
      dataType: 'json',
      success: function(data, status) {
        alert(data.dbid);
      }
    }
  );
  return false;
}

createLocationAjax라는 함수를 하나 정의하고 있는데, 이 함수는 options라는 프라퍼티 객체를 인자로 받는 함수입니다. 이 프라퍼티 객체를 통해 url, elementId, pageX, pageY, width, height 등의 값을 인자로 전달할 수 있고, ajax 호출이 정상적으로 수행되었을 경우의 핸들러와 실패했을 경우의 핸들러도 인자로 전달할 수 있습니다. 위의 함수는 전달받은 인자들을 가지고 json 객체를 만들어, 그 객체에 대한 참조를 data라는 프라피터의 값으로 두고 $.ajax 함수를 호출합니다.

유의할 것은 서버측에서 params[:location]과 같은 형태로 클라이언트가 전송한 인자들을 가져와 분석하여 객체를 생성하기 때문에, 클라이언트측에서도 그에 맞게 json 프라퍼티 이름을 설정해 주어야 한다는 겁니다. rails의 form_for 태그가 생성하는 HTML form 코드를 보셨다면 아마 쉽게 이해하실 수 있을 듯. 따라서 모든 json 프라퍼티 이름은 location[xxx]의 형태로 지정하였습니다. 그래야 서버쪽에서 :location을 키로 사용하여 클라이언트가 전송한 모든 파라메터들을 한번에 가져올 수 있죠.

각설하고, 이렇게 하는 것 만으로 클라이언트쪽에서 생성한 모델 객체(!)의 위치 정보를 서버에 지속적(persistent)으로 저장할 수 있습니다. 저장이 잘 되었을 경우, 서버에 생성된 location 데이터 투플의 ID 필드값이 반환되기 때문에, (위의 코드에서 data.dbid가 바로 반환된 ID 필드값) 클라이언트 측에서는 그 ID를 사용해 update 같은 다른 작업도 할 수 있게 됩니다.

물론 이런 식으로 코딩하게 되면 웹 프로그래밍을 하는 사람이 자바 스크립트도 알아야 하고, rails도 쓸 줄 알아야 한다는 부담이 있긴 합니다만, rails 코딩에 드는 부담이 굉장히 낮기 때문에 오히려 JavaScript만 알더라도 간단하게(?) 웹 프로그래밍을 할 수 있게 됩니다. 웹 서버를 그냥 persistency API를 제공하는 서버 정도로 생각해버려도 되는데다, 사실 Ruby에 대해서 잘 몰라도 되거든요.

물론, 이 방법이 100% 완벽한 것은 아닙니다. 최신 버전의 rails에는 클라이언트 측 공격을 막기 위한 feature가 들어 있는데, (그에 관해서는 http://www.buggymind.com/148 를 참고하세요) 이 기능과 연동하기가 어렵습니다. rails API를 사용해 폼을 생성하거나 하지 않기 때문이죠. 따라서, 위의 예제가 제대로 돌아가기 위해서는 locations_controller.rb 파일의 맨 위쪽에 다음과 같은 코드를 추가해 주어야 합니다.

class LocationsController < ApplicationController
  skip_before_filter :verify_authenticity_token

  ...
end

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^