본문 바로가기

카테고리 없음

Angular JS vs Ember (수정중)

(** render 렌더링 표현에 대해 수정)


 최근 나는 주변 개발자 몇명과 함꼐 클라이언트 단 MVC 프레임워크에 대한 토론을 가졌다. 토론은 결국 AngularJS와 Ember 사이의 차이에 대한 토론으로 귀결되었다.

 

 Discourse는 Ember 애플리케이션이며, 첫번쨰 프로토타입이 나온 상태이다. 그래서 나는 이것에 대하 많은 경험을 가지고 잇다. 그렇지만 나의 동료들과 대화과 진행될 수록 분명해지는 것은 내가 AngularJS에 대해 모르는 것이 많다는 점이었다.

 

 개발자들의 관심의 점유율면에서 AngularJS가 Ember를 이기고 있다는 증거가 있다 : Stack Overflow에 올라오는 질문부터 AngularJS에 관한 것이 더 많다. AngularJS는 Github에서 더 많은 별점과 (****) forks를 가지고 있다. 최근에 토론토에서 열린 Javascript meetup에서 가상 투표를 진행했을 떄 모든 개발자들이 AngularJS에 대해 더 많이 배우는 것에 관심을 보였다. 이 프레임워크에는 분명히 뭔가 있는 것이다!

 

 나는 시간을 들여서 AngularJS에 대해 연구를 해보기로 결심했고, 이것에 대해 왜이리 호들갑인지를 알게됐다. 가능한 많은 문서와 블로그 포스트들과 가이드들을 읽어보았다. AngularJS를 다운받아서 간단한 애플리케이션도 만들어보았다.주변 개발자들에게 연락을 해서 프레임워크에 관한 질문으로 그들을 귀찮게도 해보았다.

 

  나는 왜 AngularJS의 인기에 가속도가 붙고 있는지 알게 되었다 : 단순하다. 프레임워크에는 많은 기능이 있지 않다. 결과적으로 배워야할 것 역시 적다. 만약 내가 클라이언트 사이드의 MVC 프레임워크의의 도구의 다양함으로 순위를 정한다면, Angular는 Backbone과 Ember 사이의 어디쯤에 위치할 것이다. 



단순함이 가지는 위험성


 몇년 전에 많은 Rails 개발자들이 Sinatra에 관심을 갖고 있다는 것을 알게되었다. Sinatra는 Rails보다 훨씬 단순하다. 이것은 Rails에 비해 객체의 일부만을 할당하였다. 나는 그런 개발자들이 그들의 애플리케이션의 빌드를 시작하는 패턴을 관찰했다. Rails의 편리한 함수들의 큰 도구함에 익숙한 사람들은 ActiveSupport를 include했다. ORM이 필요한 이들은 ActiveRecord를 include했다. 이렇게되면 Sinatra의 장점인 부분이 사라졌다. 기본적으로 Rails 애플리케이션을 돌리는 것과 같다. 


 애플리케이션을 단순하게 유지할 때에 프레임워크의 단순함은 좋은 것이다. 그렇지만 애플리케이션의 규모가 커질 가능성이 있고, 오랜기간 동안 유지보수를 해야한다면 단순함을 선택할때에 좀 더 신중할 필요가 있다.


 오해는 하지마라 : 만약 한 API가 뒤얽혀있고, 장황한데 이와 대비되는 것이 기능적으로 동등하면서 사용하기에 단순하다면, 당연히 대비되는 쪽을 사용하는 것이 낫다. 그렇지만 사람들은 좋은 디자인을 위한 단순함에 대해 너무 빠르게 오해하기도 한다. 


 Ember는 이해를 위해 학습이 필요한 개념들이 AngularJS보다 많다. Ember의 복잡성으로 인해 이를 버리기 전에, 왜 추가적인 라이브러리들을 덧붙여야 하는지에 대해 고려해봐라. 그것이 이유가 있는 것인가?


 Ember는 대형, 유지보수가능한 애플리케이션을 빌드하려고 할 때 필요한 모든 개념들이 담겨져 있는 도구함이다. 트레이드 오프는 당신의 코드를 건전한 방식으로 구조를 잡는 것을 도와주는 API들이 만들어져 있다는 것이다. 만약 당신이 Ember를 학습한다면, 당신은 AngularJS에는 없는 몇가지 철학과 의견들을 알아챌 수 있을 것이다. 


 이것은 예를 들어 설명하는 것이 훨씬 좋을 것이다. 이 포스트에서 나는 AngularJS의 몇가지 중요한 단점들의 예를 들고, Ember를 통해 더 나은 방식으로 접근하는 것에 대해 설명할 것이다.



single source of truth를 추구의 관점에서 

 

 당신이 서버에서 렌더링되는 웹 애플리케이션에서 작업을 하고 있다고 해보자. 당신이 작업 중인 현재 페이지는 일렬로 항목들의 리스트를 보여주려고 하고, 사용자가 한 열을 클릭하면 그 열은 선택됐음을 알려주는 색깔로 변화될 것이다. 


 당신이 만약 jQuery로 이것을 하려고 한다면, 각 열에 클릭 이벤트를 걸기 위해 함수를 바인딩할 것이다. 그리고 이벤트가 발생되면, 함수는 열의 css 클래스를 변경해서 하일라이트 처리를 할 것이다. 


 물론 여기서 멈추지 않을 수도 있다. 함수는 또한 이전에 선택된 다른 열들에서 css 클래스를 제거해야 할 수도 있다. 이것에 대해 생각하는 것은 약간 복잡한 일이다! 선택된 항목을 변경한다는 것은 우리가 UI에서 표현되는 모든 곳을 알고 있어야 한다는 뜻이다. 


 당신이 같은 종류의 데이터 여러 곳에 저장하려고 한다면, 동기(sync)가 어긋날 수도 있다. 추가적으로 변경사항이 있을 때마다 다양한 모든 업데이트를 관리해야한다면 당신의 코드는 더 길어지고 더 복잡해질 것이다. 

 

 Ember와 AngularJS와 같은 클라이언트 사이드 MVC(Model View Controller)프레임워크는 표현으로부터 데이터 모델을 분리한다. 당신의 모델은 single source of truth가 되는 것이다. 만약 당신이 바꾸려고 한다면, 당신의 HTML 템플릿은 자동적으로 변경될 것이다. 만약 당신이 현재 값을 알고 있다면, 그것을 DOM 대신 모델에서 읽어올 수 있다. 


 이것은 개발자의 생산성에 있어서 매우 유용하며 동시에 데이터 동기화(sync)가 끊어지는 것과 관련한 잠재적에러를 감소시킬 수 있다. 


 AngularJS의 모델이란 무엇인가?


 AngularJS는 스스로를 MVC 또는 MVW(Model View Whatever) 프레임워크라고 설명하고 있다. 


 AngularJs의 view가 어디에 있는지는 명확하다 : 일반적인 HTML 문서에 ng-* 어트리뷰트와 핸들바 스타일 {{variable}} 표현으로 주석을 달 수 있습니다. controllers가 어디에 위치하는지도 알기 쉽습니다. 자바스크립트 클래스를 ng-controller 어트리뷰트로 엘리먼트에 연결시키면 됩니다. 


 명확하지 않은 것은, 특히 당시이 서버 사이드 MVC 경력이 있는 사람이라면, AngularJS의 모델이 어떤 것이지? 하는 것 입니다. Model 베이스 클래스에 대한 표준도 없고, 모델이 어떠해야하는지를 정의한 콤포넌트나 인터페이스도 없습니다. 


 AngularJS Controller에서 $scope라는 객체가 주어집니다. 데이터를 여기에 attach시키면 HTML 템플릿에서 렌더링이 됩니다. 


function SomeCtrl($scope) {

   $scope.countries = ['can', 'usa', 'fra', 'jap'];

   $scope.user = {name : "Evil Trout"};

   $scope.age = 34;


   // 템플릿은 이제 {{age}}, {{user.name}} 과 countries의 리스트를 렌더링 할 수 있다!!

}


 AngularJS 문서에 따르면 AngularJS $scope에 덧붙여진 데이터가 모델이며, 단순한 자바스크립트 객체나 배열일 뿐만 아니라, 원시 객체이기도 합니다. 위의 스니펫에서는 세 개의 모델이 존재한다! 



자바스크립트 원시객체가 모델로서 부적합한 이유 


 AngularJS는 템플릿에 single source of truth를 관리할 수 있는 툴을 제공한다. 이것은 데이터 바인딩으로 알려진 개념이다. 만약 AngularJS 표현인 {{age}}를 가지고 있는 템플릿을 만들었다면, 이것은 $scope.age에 바인딩되었다고 말할 수 있다. 만약 {{age}}를 한 템플릿에서 여러번 작성하고, controller에서 $scope.age = 40을 실행했다면 모든 것이 동시에 업데이트 될 것이다. 


 (***)그렇지만 이것은 두번째 레벨의 데이터 바인딩이다. 진짜로 원했던 것이 single source of truth 라면 이것은 데이터 모델 자신 안에 있는 것이다. 다시 말하면 AngularJS는 오직 $scope와 template 사이에서 데이터 바인딩이 일어날 때에만 작업을 중단한다. 자바스크립트 코드 안에 있는 구조에서는 그렇지 않다. 


 Ember에서, 모든 모델은 Ember.Object 기본 클래스의 확장이다. 이렇게 하면 모델 간의, 모델 내부의 관계를 선언할 수 있게 된다. 예를 들어 :


 App.Room = Ember.Object.extend({

   area : function() {

      return this.get('width') * this.get(height');

   }.property('width', 'height')

});


 여기서는 Room이라는 객체를 생성했다. 또 area라고 하는 computed property를 선언했다. 끝에 있는 property 문법이 Room의 area가 Room의 width와 height에 의존하는 Ember 라는 것을 말해준다. 


 이 모델의 인스턴스를 생성하는 것도 쉽다. 


var room = App.room.create({width : 10, height : 5});


 이것으로 템플릿을 만들 수 있다.


<p>Room:</p>

<p>{{width}} ft.</p>

<p>{{height}} ft.</p>

<p>{{area}} sq ft.</p>


 이제 Ember는 어트리뷰트들을 정확하게 렌더링할 것이다. 이 경우에 area를 width와 height와의 연결에서 떨어뜨리는 것은 불가능하다. 만약 두 프로퍼티 중 하나라도 변경이 되면, area는 자동적으로 업데이트 되게 된다.



 AngularJS에서의 this 모델링 

 

 AngularJS 모델은 일반적인 자바스크립트 객체이기 때문에, AngularJS는 computed property와 같은 것을 갖지 않는다. 대신 객체에서 함수를 사용하여 유사한 것을 만들 수 있다. 


var Room = function(args) {

   this.width = args.width;

   this.height = args.height;

}


Room.prototype.arae = function() {

   return this.width * this.height;

}


 Room의 area에 접근하기 위해서는 area() 호출 시에 한 쌍의 괄호를 추가해야한다. 


<p>Room : </p>

<p>{{width}} ft.</p>

<p>{{height}} ft.</p>

<p>{{area()}} sq ft.</p>


 Ember와 AngularJS의 중요한 차이점에 대해서 생각해보자. Ember는 Uniform Access Principle을 따르고 있다. Ember 템플릿에서 computed나 원시 객체에 접근하던지 간에 표현 방식은 같다. AngularJS에서 함수는 명확하게 구분되어 진다.


 이것은 끝없는 유지보수의 악몽으로 이어질 수 있다. 거대 소프트웨어 프로젝트에서 시간이 지나면 이전에 작성되었던 것들을 변경하고 싶어질 수 밖에 없다. Ember에서는 고통없이 이것을 할 수 있다. AngularJS에서는 모델이 사용되는 모든 템플릿을 업데이트해야한다. 



 Getter와 Setter의 사용 


 (**)위의 Ember 코드에서 알아챌 수 있는 것의 트레이드 오프 대해서 논쟁해볼 가치가 있는 일이다.  모델의 프로퍼티에 접근하기 위해서는 getter와 setter를 사용해야만 한다. 이것은 약간의 추가 타이핑을 의미하지만, 자바스크립트 코드로 템플릿에서 하던 것과 같은 이점을 얻을 수 있다. 함수로 원시객체를 대체하는 것이 가능하다. 

 

 getter와 setter를 사용하는 두번째 이점은 안전하게 체이닝할 수 있다는 것이다. 아래의 코드에 대해 생각해보라. 


console.log(room.inhabitant.name);


 inhanitant가 존재하지 않는다면 무슨 일이 일어날까? 자바스크립트 에러가 발생할 것이다. Ember에서 같은 방식으로 undefined를 만나게 된다면, 쉽게 이를 좀 더 유연한 코드로 만들 수 있다. 


// undefined를 출력한다

console.log(room.get('inhabitant.name'));



성능 이슈


 computed property 대신에 함수를 사용하는 것에는 또 다른 단점이 있다. 매우 느리다는 점이다. 


 Ember 코드에서, 우리는 area를 width와 height에 의존해서 표현했다. AngularJS에서는 그렇게 하지 않았다. 그러면 width와 height 값이 변할 때 re-render해야한다는 것을 AngularJS는 어떻게 알까? 값이 변화할 때 모델로 쓰일 특별한 자바스크립트 객체를 사용하지 않고 어떻게 re-render해야한다는 것을 알아낼까?


 답은 다음과 같다 : 알지 못한다. AngularJS는 DOM이 업데이트 되야한다는 것을 판단하기 위해 dirty checking 이라고 불리는 과정을 사용한다. controller가 코드 실행을 마치면, AngularJS는 $scope 안에 있는 모든 값을 이전 값과 비교한다. 만약 값이 바뀌었다면, DOM을 업데이트 한다. 


 AngularJS는 함수가 무엇을 하는지 모르기 떄문에 함수를 다시 실행해야 할지 말지를 알 수가 없다. 그래서 템플릿을 업데이트를 해야할 떄코드를 매번 실행시켜야 한다. 한 템플릿에서 함수가 수백, 수천번 실행이되면 어떻게 될까? 만약에 함수가 단순한 계산을 하는 것이 아니라면? 아마 매우 느려질 것이다! 


 나는 주변의 AngularJS 개발자에게 이것에 관해 물어보았고, 최고의 해결책은 다른 변수 안에 함수의 결과를 직접 넣어놓는 것인 것이 명확했다. 그렇지만 그렇게 한다면, 그 값에 대해서 one source of truth가 어긋나게 되는 것이다. 만약 당신이 Room의 넓이를 업데이트한다면 , area 프로퍼티 변경된다는 것을 어떻게 보장하겠는가?


 나는 AngularJS 함수인 $scope에 대해 지적했다. $watch는 당신이 표현을 감시하고, 그것이 변화할 때 어떤 코드를 실행시키도록 해준다. 문제는 $watch함수는 Room이 아닌 $scope에 속한다는 것이다. 예를 들어 Room의 인스턴스의 배열을 가지고 있다면, 지속적으로 루프를 돌지 않는다면 감시를 할 수 없습니다.

 

 만약에 Room 객체의 인스턴스와 같은 것이 복수의 controller 안에 존재한다면? 모든 controller에 $watch를 가지고 있어야 하고 이것은 많은 수의 불필요한 재계산을 의미한다. 만약 이런 접근 방법을 선택했다면, 이슈 디버깅이 잘되길 바라겠다! 유지보수의 악몽에 대해 말하게 될 것이다! 



객체 인스턴스의 재사용


 AngularJS는 Ember에 비해 객체 인스턴스의 재사용을 어렵게 만들었다. 예를 들어 Ember 템플릿에서 {{linkTo}} helper를 사용해서 다른 라우터에 연결을 시킬 수 있다. 


<ul>

{{#each user in Users}}

   <li>{{linkTo 'Users.show'}}Show {{Username}} {{/linkTo}}</li>

</ul>


 여기서는 Users의 리스트를 돌고 있으며 특정 user에게 보여주기 위해 linkTo를 생성하였다.  만약 link 위에 마우스를 올려놓았을 때 route가 적절하게 셋팅되어있다면 users/show/123 과 같은 것을 볼 수 있을 것이다. 그렇지만 link를 클릭한다면, Ember는 실제로는 user에 대한 참조를 다른 라우터에 상속시키게 된다. 


 오랫동안 사용되어 온 브라우저 애플리케이션의 큰 장점 중의 하나는 (****)사용자가 페이지를 이동할 때 객체를 재사용할 수 있다는 점이다. AngularJS는 이런 철학을 따르고 있지 않다. AngularJS는 이미 가지고 있던 것을 내다버리고, 그것을 다시 찾아오도록 만들고 있다.(아마도 서버에서 말이다!)


 이것에 대한 또 다른 결론은 만약 당신이 메모리에서 같은 User 객체의 두 인스턴스를 제거했다면, 이것은 single source of truth를 위반했다는 이야기라는 것이다!


 결론


 개인적인 생각으로는 단순함에 초점이 맞춰진 Angular는 몇가지 심각한 결과를 가지고 올 수도 있는 것으로 보인다. 엄격한 규칙을 지키면서 스스로 할 수 있는 해결책들 역시 존재한다. 그렇지만 스스로에게 한 번 물어보라 : 당신의 팀의 모든 개발자들이 같은 콘벤션을따를까? 게다가, AngularJS를 Ember처럼 동작하게 만들기 위해 추가적인 구성이 필요하다면, 처음부터 Ember를 쓰는 것이 낫지 않을까?


 콘벤션의 부족으로 인해, 수많은 Angular 프로젝트들이 Ajax 호출을 직접적으로 controller 안에서 하는 등의 배드 프렉티스들이 너무나도 많다. 의존성 주입(DI)으로 인해 directive안에 

라우터 파라미터를 주입하고 있는가? 초보 AngularJS 개발자들은 코드 구조를 잡기 위해서 숙련된 개발자들이 자연스럽다고 믿는 방식으로 코딩을 해야하는가?


 그렇다면, 자연스러운 AngularJS라는 것은 무엇인가? 내가 읽은 예시나 블로그들 중에서 어떻게 객체 인스턴스 재사용을 할 지 보여주는 것은 없었다. 이것은 AngularJS 애플리케이션에서 아직 쓰인 적이 없는 것일 뿐일까?


 궁극적으로, 당신은 애플리케이션의 목표를 설명해야 한다. 사람들이 웹에서 기대하는 이상의 것을 빌드하고 싶은 것인가? 만약 당신의 애플리케이션이 극도로 단순한 형태여야 하거나, 강력한 기능을 추가하고 오랫동안 사용되는 애플리케이션으로 만들고 싶은가?


 만약 프론트 엔드 개발을 진지하게 생각한다면, 나는 Ember를 공부하는 것이 더 적절하다고 말해주고 싶다. 모든 조각이 어떻게 맞춰져야 하는지 이해하게 된다면, 그것들 없이는 살 수 없게 될 것이다. 



(원문 : http://eviltrout.com/2013/06/15/ember-vs-angular.html )