Rev. 2.73

highres_4518073.jpg

회사에서 첨여중인 프로젝트의 시제품(prototype)에 SproutCore 자바스크립트 프레임웍을 적용한 것을 시작으로, 아주 조금씩 조금씩 작동원리를 이해해 가면서 즐거운 나날을 보내고 있습니다. 그렇게 약 2개월 정도 작업이 진행되었고 큰 그림이 머리속에 어렴풋이 그려지고 있는 단계인데요. 알면 알수록 이 자바스크립트 프레임웍은 잘만들었다는 생각이 듭니다. 자바스크립트 프로그래밍을 완전한 MVC(Model View, Controller)패턴으로 작성할 수 있게 하기 때문입니다.

MVC 철학의 핵심은 바로 바인딩(binding)입니다. 바인딩 덕분에 개발자는 속성 값이 변할 때 자동적으로 자바스크립트가 실행되도록 작성할 수 있습니다. 바인딩이 있으면, 고도로 일관성 있는 비헤이비어로 구성된 매우 복잡한 애플리케이션도 "연결(glue)" 코드를 거의 작성하지 않고 만들어 낼 수 있습니다. 무슨말이냐고요? 차차 알려드리기로 하고, 먼저 SproutCore의 정체를 밝혀 볼까요? SproutCore가 애플사에서 개발한 프레임웍인 것으로 오인하는 분들이 있는데요. SproutCore는 "Charles Jolley"씨에 의해 개발되었으며, 코코아(Cocoa)에서 영감을 받아 만들어진 자바스크립트 프레임웍입니다. 오픈소스와 개방형 웹표준에 기반을 두기 때문에, 특정 플러그인이나 기업에 얽매이지 않은 크로스 플랫폼 애플리케이션 개발을 할 수 있습니다. Jolley씨는 애플의 .Mac 팀에서 일하고 있으며 그의 프레임웍을 지속적으로 개선시키는 작업을 하고 있습니다. 그래서인지 기본으로 제공하는 뷰의 모양새가 애플의 것과 상당히 닮아 있습니다. 대표적으로 애플의 "iWork"와 "MobileMe"서비스가 SproutCore 자바스크립트 프레임웍을 기반으로 구축되었습니다.

20090630-ba818imqkhgiicwf4dhwcfg4ps.png

근 20년동안 클라이언트 애플리케이션을 구축하는데있어서 MVC는 매우 성공적인 디자인 패턴이라는 것을 증명했습니다. 하지만 SproutCore는 웹 브라우저에서 실행되는 애플리케이션이기 때문에 몇가지 특별한 것들이 추가되어 있습니다. 이것은 모델 레이어을 지원하기 위한 3개의 추가적인 레이어들로 구성됩니다. 이것이 바로 MVC+SDR 아키텍처입니다. SDR은 Server Interface, Display, Responders를 의미하며 Server Interface는 웹서버와 모델 레이어간에 데이터를 송/수신 하는 것을 핸들링하고 클라이언트-사이드 스토리지를 사용하여 오프라인 모드를 지원하는 것이고, Display는 로우-레벨 DOM 라이브러리(jQuery 또는 Prototype)들을 포함하고 뷰들(Views)이 이를 순조롭게 이용하여 HTML을 랜더링하도록 중계하는 것이며, Responders는 애플리케이션의 전반전인 상태를 제어하는 레이어로써, 탑 레벨에서 구성한 모델, 뷰 그리고 컨트롤러의 부하 상태 등을 점검합니다.

지금부터 제가 이해한 SproutCore의 작동 과정을 설명해 보겠습니다. SproutCore는 매우 유연하게 작성할 수 있도록 만들어진 프레임웍입니다. 아래는 특정한 서비스를 구현하는 과정의 일례이며, 그 구조를 이해하기 위한 것이므로 설치 및 사용방법 등에 관련해서는 별도로 작성하겠습니다. 때문에 자세한 코드를 기술하지 못하는 점은 양해해 주세요. 코드들 중 '...'은 중략을 의미합니다.

Model Layer :

모델 레이어에서는 대부분의 비지니스 로직을 처리합니다. SproutCore는 서버로부터 수신한 자료형 데이터를 어떠한 방법으로든(Inline Script Tag 또는 AJAX JSON 또는 JSONP) 한번에 묶어서 수신하고 클라이언트 사이드에서 자바스크립트가 초기화(Initialize) 되는 순간 타입(Type)으로 구분하여 관련 메서드들을 모델에 할당하고 인스턴스로 만들어 메모리에 보관합니다. 자료형이 다루어지는 모습을 살펴봅시다. 서버에서 수신한 자료의 예를 들면 아래와 같습니다.

{
  records: [
    {
      type: 'Event',
      guid: 1,
      title: '주간 업무보고',
      calendarGuid: 1
      startDate: 1248652800000,
      ...
    },
    {
      type: 'Event',
      guid: 2,
      title: '브레인스토밍',
      calendarGuid: 1
      startDate: 1248678000000,
      ...
    },
    {
      type: 'Calendar',
      guid: 1,
      title: '회사',
      description: '주당 100',
      ...
    },
    ...
  ],
  preferences: [
    ...
  ]
}

이렇게 무작위로 수신한 자료들이 초기화 과정을 거치면서 개개의 자료가 가진 프로퍼티들이 유효한지 확인하거나 다른 형태로 변환되거나 가공되기 위한 메서드가 할당되어 버립니다. 다음 예제는 메모리에 올려둔 자료를 호출하는 방법입니다.

var MyCalendar = MyProject.Calendar.find({
  guid: 1
});
var MyCalendarGuid = MyCalendar.get("guid");
var MyEvents = MyProject.Event.findAll({
  calendarGuid: MyCalendarGuid 
});

달력들 중에서 guid가 1번인 것을 찾아내고 이벤트들 중에서 찾아낸 1번 캘린더에 속했는 자료들를 가져오는 코드입니다. 실행되면 MyEvents 변수는 아래와 같은 컨트롤러가 반영된 결과물을 얻습니다.

[
  Record({
    type: 'Event',
    guid: 1,
    title: '주간 업무보고',
    calendarGuid: 1
    startDate: 1248652800000,
    ...
  }),
  Record({
    type: 'Event',
    guid: 2,
    title: '브레인스토밍',
    calendarGuid: 1
    startDate: 1248678000000,
    ...
  })
]

SproutCore로 생성된 모든 객체(모델, 뷰, 컨트롤러)에는 "get", "set"메서드를 통하여 값을 주고 받아야 합니다. "set"메서드는 두번째 인자로 값을 받습니다. 이 두 메서드들를 통하여 이벤트를 발생시키거나 값을 호출하고 저장하기 때문입니다. 다음은 자료형에 할당된 메서드들 중 밀리세컨즈로 기록된 시간을 알아보기 쉽게 만들어 주는 "formattedStartTime"함수를 작성했다고 가정하고 호출하는 예제입니다.

MyEvents.get("formattedStartTime");
//-> ["9:00 AM", "4:00 PM"]
MyEvents[0].set("startDate", MyEvents[0].get("startDate") + 10800000);
//-> 1248663600000
MyEvents[0].get("formattedStartTime");
//-> 12:00 PM"

View Layer :

다음은 SproutCore의 뷰 레이어를 살펴보겠습니다. 뷰는 최초 로드한 페이지에 "resources"라는 ID속성을 가진 DIV요소 속에 마크업 되어있는 HTML 코드들이 그 대상이 됩니다. 여기에 속해있는 요소들은 다이얼로그, 레이아웃, 버튼, 패널, 폼, 컨텍스트 메뉴 등 다양한 방법으로 표현될수 있는 기능단위 요소들입니다. 뷰역시 모델과 마찬가지로 초기화 될 때 뷰 메서드들이 할당되고 인스턴스화 되어 메모리에 보관됩니다. 뷰가 가지는 메서드는 요소가 그려지는 역할을 담당하도록 구성되어 있습니다. 즉 DOM과 관련 메서드가 결합되어 하나의 "뷰"가 되는 것입니다. 다음 예제는 일정 목록을 간략하게 보여주기 위해 준비된 패널의 마크업입니다.

<div id="resources" style="display:none; visibility: hidden;">
  <div id="event_list_panel" class="pickerright pickerbottom" style="width: 225px;"> 
    <div class="event_list_picker_wrapper"> 
      <div class="event_list_title shared_dialog_title"> 
        <span class="sc-label-view event_list_title_label"></span> 
      </div> 
      <div class="event_list_body">  
        <div class="overflow_events"></div> 
      </div> 
    </div> 
  </div> 
  ...
</div>

위 DOM요소는 다음과 같은 초기화 과정을 거칩니다. 뷰를 지정함과 동시에 "outletFor"(DOM Selector)메서드를 이용하여 노드를 찾아내고 변수에 담습니다. 그리고 실제 DOM요소는 문서영역에서는 제거됩니다. 이 때 프로퍼티로 생김새을 결정하거나 출력될 문자를 설정하거나 값이나 컨트롤러를 바인딩하는 것을 정의할 수 있습니다.

SC.page = SC.Page.create({
  eventListPanel: SC.View.extend({
    outlets: ["eventListTitleLabel","overflowEvents"],
    eventListTitleLabel: SC.LabelView.extend({
      valueBinding: "MyController.panelController.userName",
      localize: false,
      escapeHTML: true
    }).outletFor(".event_list_title_label?"),
    overflowEvents: MyView.OverflowEventsView.outletFor(".overflow_events?"),
    isVisibleBinding: "MyController.panelController.hasUsers",
    paneType: "panel"
  }).outletFor("#event_list_panel"),
  ...
});

SproutCore는 웹 브라우저에서 표현할수 있는 대부분의 기본적인 뷰와 컨트롤을 제공합니다만, 독특한 뷰가 필요하다면 기존뷰를 확장하거나 새로운 뷰를 얼마든지 만들어 낼 수 있습니다. 기본적으로 마우스/키보드/윈도우 이벤트 헨들러를 작성하여 뷰에 변화를 줄 수도 있고, 밸류 바인딩을 통하여 변화를 주는 것도 가능합니다. "isVisibleBinding"은 대상이되는 변수의 값이 참인 경우 나타나며, 그렇지 않은 경우 숨깁니다. "valueBinding"은 대상이되는 변수의 값을 출력합니다. 자, 이것이 SproutCore의 핵심입니다. 대상이되는 변수를 조작하기만 하면 즉시 바뀐 값을 출력하고 숨거나 나타납니다. 이 바인딩의 개념을 잘 이용하면 매우 간단하게 스스로 살아 움직이는 DOM요소를 만들어 낼 수 있게 되는 것입니다.

 MyView.OverflowEventView = SC.View.extend({
  ...
  render: function(j) {
    this.set("isVisible", true);
    this.setColors();
    ...
  },
  mouseDown: function(event) {
    MyController.panelController.hitTest();
  ...
});

Controller Layer :

위와 같이 고도로 정밀하게 디자인된 모델과 뷰를 잘 버무려주는 녀석이 바로 컨트롤러 입니다. 딱히 정해진 롤이없고 99.9% 커스토마이즈 영역이기 때문에 적절한 예를 들기가 모호하군요. 주요한 역할은 모델을 찾아내고 뷰와 조합해서 되돌려 주거나 시시각각 변화하는 복잡한 인터랙션을 처리합니다.

MyController.panelController = SC.ObjectController.create({
  hasUsers: false,
  userName: null,
  hitTest: function() {
    this.hasUsers = !!MyEvents.findAll({}).length;
  ...
});

var displayName  = MyEvents.find({ownerEmail: "to@firejune.com"}).get("displayName");
MyController.panelController.set("userName", displayName);

이 외에도 설명하지 못한 엄청난 것들이 엄청나게 많습니다!

Comments

Got something to add? You can just leave a comment.

  • OR 맵퍼 같기도 하고, 신선하긴 합니다만, 레이아웃까지 계속 주무르다가 extjs 처럼 무거워질것 같습니다. 바인딩이야 extjs 가 훨씬 훌륭하지 않은가요?

    reply edit

  • 다른 자바스크립트 프레임웍와 비교하기위한 포스팅은 아닙니다만, 이 포스트를 작성한 이유는 "SproutCore를 사용해야한다"가 아니라, 작동원리를 이해하고 자신의 코딩스타일에 변화를 주기위해서 입니다. 그리고 SproutCore의 바인딩은 단순히 이벤트나 함수가 트리거에 의해 작동하는 개념을 넘어서고 있습니다.

    reply edit

  • 딱 보니까 서버사이드에서 유행하는(?) ORM을 자바스크립트로 옮겨놨다는 생각이 드는군요;; 아이디어는 괜찮은데 성능이 얼마나 나와줄지가 관건 아닐까 싶습니다.

    reply edit

  • SproutCore를 이용하여 전형적인 웹성격의 게시판이나 블로그를 작성하는 것은 SproutCore를 선택한 자체가 오류라고 생각합니다. 데스크탑 애플리케이션과 같은 매우 복잡한 인터랙션을 가진 웹 프로그래밍을 구현하려면 그만한 대가가 필요하지 않을까요? 개인적인 생각으로는 불규칙한 시점에서 DOM을 생산하고 이을 중심으로 바인딩하는 자바스크립트 프레임웍들에 비해서는 성능이 좋을 수밖에 없다고 생각하고 있습니다. 초기화와 동시에 사용할 준비가 완료된 셈이니까요. 초기화 과정이 매우 무겁다는 것이 단점이라면 단점이겠습니다.

    reply edit

  • 이 글만 봐서는 별다른 장점을 못 느꼈는데, 쭉 올려주신 다른 글들을 보니 좀더 확실하게 감이 오는군요. ^^; 무엇보다 상태 변화를 모니터링해준다는 점이 장점인 듯합니다.

    reply edit

Your Reaction Time!

captcha

avatar