MVC에 대한 오해

angular.js 등의 자바스크립트 MVC 프레임웍을 보면 MVC에 대한 오해가 하나 보인다. 원래 고전적인 MVC에서 말하는 Model은 비즈니스 모델 객체를 말하는 게 아니라 View에 맞는 데이터를 담고 있는 Model이다. 그런데 웹 스타일의 MVC가 더 흥하면서 개념이 약간 바뀌기 시작했는데, 고전적인 MVC로 돌아가려는 자바스크립트 MVC 프레임웍들은 그 개념 변경을 이해하지 못한 상태에서 돌아가는 중이다. 예를 하나 들어보자.

상품 정보가 Product 객체에 담겨 있고 이걸 목록 형태, ListView에 뿌려주고 싶다. 이럴 때 웹 스타일의 MVC가 하는 방식은 이렇다. 컨트롤러에서 사용자의 요청을 받은 다음 뿌려줄 Product 목록을 가져와서 HTML 템플릿으로 된 View에 하나씩 뿌려준다. 이를테면 다음과 같은 식이다.

Controller

def products(request):
products = Product.objects.all()
return render_to_response('products.html', locals())

View

% for product in products:
<li>${product.name}: ${product.price}</li>
% endfor

여기서의 Product는 고전적인 MVC에서 말하는 Model이 아니다. 그런데, 웹에서는 이 방식이 더 간편하기 때문에 이 방식이 더 널리 퍼지면서 MVC의 Model이 비즈니스 엔티티를 일컫는 말로 바뀌기 시작했다.

그럼 고전적인 MVC는 어떤가? 다음과 같은 식이다.

def on_products_button_click():
model = find_view_by_id('products').model

for product in Product.objects.all():
model.append([prodcut.name, product.price])

model.notify_changed()

def delete_product(id):
Product.objects.get(id=id).delete()
model = find_view_by_id('products').model
model.delete(lambda e: e.id == id)

model.notify_changed()

이런 방식은 자바의 swing이나 awt에서 테이블, 리스트 등의 뷰를 보면 알 수 있다. 안드로이드의 ListView - ListAdapter도 고전적인 MVC의 View-Model 관계에 가깝다. iOS의 UITableView와 UITableViewDataSource의 관계도 View-Model 관계다. 물론 이것도 전통적인 MVC에 비하면 Model에 View가 상당히 섞여들어가는 구조다. 전통적인 MVC는 ListView 등에 쓰였고, ListView의 Cell을 자유자재로 그릴 수 있는 형태가 아니었다. 아무튼, ListAdapter든 UITableViewDataSource든 그 자체가 비즈니스 엔티티인 게 아니라 별도의 비즈니스 엔티티는 따로 있다. 그건 MVC의 Model은 아닌 거다. MVC의 Model은 아무 View에나 붙일 수 있는 Model이 아니라 특정 View의 형태에 종속된다.

그런데 angular.js는 다음과 같은 식이다.

View

<li ng-repeat="product in products">
{{product.name}}: {{product.price}}
</li>

Controller

$scope.delete_product = function(id) {
Product.query({id:id}).delete()
}

이러면 자동으로 $scope.products에 product가 지워지고 서버로 DELETE 요청도 날아가고 view도 업데이트된다. 얼핏 생각하면 코드가 더 짧고 자동으로 되니 더 좋은 거 아닌가 싶을 수 있다. 더 좋은지 아닌지는 다음에(언제?) 다시 보기로 하고, 일단 여기서 중요한 건 Product는 MVC의 Model이 아니라 비즈니스 엔티티인데 이걸 업데이트하면 자동으로 view에 notify가 가도록 되어 있다는 것이다. MVC를 살짝 오해하고 있는 것이다.

고전적인 MVC는 View와 Model을 분리한다는 의미가 그리 크지 않다. 어차피 Model은 View에 종속적으로 설계되고 비즈니스 엔티티는 별개로 필요하기 때문에 이미 다루어야 하는 컴포넌트가 3개가 아닌 4개다. iOS를 생각하면 쉽다. 비즈니스 엔티티는 Core Data, View는 UITableView, Controller는 UITableViewDelegate, Model은 UITableViewDataSource인 셈이다. 안드로이드든 iOS든 이런 MVC 패턴은 List처럼 사용자의 액션에 반응해서 데이터를 동적으로 보여주어야 하는 컴포넌트에 주로 쓰인다. 장단점이 있지만 안드로이드나 iOS나 MVC는 그럭저럭 잘 쓰고 있다고 생각한다.

웹 MVC에서 고전적인 MVC 방식이 필요 없었던 이유는 스크롤 등의 기본적인 UI 동작은 브라우저가 알아서 해줬고, HTML을 일단 보여주고 나면 서버사이드의 손을 떠난다는 특성상 notify의 개념이 의미 없었기 때문이다. 그래서 고전적인 MVC의 Model과 View를 합쳐서 템플릿 형태로 만들 수 있었고, 이건 고전적인 MVC보다 쉬웠다. 그러다가 노골적으로 model이란 용어를 들고 나온 Rails 덕분에 Model의 의미가 비즈니스 엔티티인 걸로 바뀌어 버렸고, 옛날옛적 MVC를 기억하는 사람들은 "어, 원래 MVC는 Model을 업데이트하면 View에 notify되는 건데..." 정도의 기억만 남아 있는 상태가 되었다. 이런 상황에서 angular.js가 이 변종 MVC와 원래 MVC를 섞어버린 것이다. MVC의 Model이 아니라 비즈니스 엔티티를 업데이트하면 View를 업데이트하는데, Model은 View 안에 녹아 있다.

아직은 이게 좋은 건지 아닌 건지 잘 모르겠다. angular.js가 나쁘다고 생각하긴 하지만, 나쁜 이유는 다른 것들이라고, 이 부분에 대한 판단은 아직 잘 서지 않는다.