제목 : ViewModel 패턴의 적용(Implementing)
Model-View-Control 패턴(MVC 패턴)은 요즘 가장 잘 알려진 패턴일 것입니다. 마이크로소프트도 ASP.NET MVC 프레임워크로 시류에 편승하고 있으며, Ruby on Rails, Django(Python) 그리고 Spring MVC 프레임워크(Java) 같은 다른 유명한 프레임워크 들도 이 유명한 패턴들을 적용하고 있습니다. MVC 패턴은 Request-Response 기반의 웹환경에 잘 맞는 패턴입니다. Request 가 들어옴에 따라 Controller 가 어떤 행동이 이루어져야 하는지 결정을 하고 Model 에게 알려주고, 렌더링에 대한 책임을 View 엔진에게 넘겨줍니다.
반면 실버라이트는 비록 웹에서 실행되지만 전통적인 웹 애플리켜이션보다는 WPF(Window Presentation Foundation)와 같은 Rich Client 애플리케이션에 가깝습니다. 이런 이유로 애플리케이션을 설계 할 때 다른 방법이 요구 됩니다. 이 포스트에서는 Model-View-ViewModel(MVVM) 패턴,(혹은 Fowler가 부르듯이
the Presentation Model) 에 대해 이야기 하려고 합니다. 저의 YouCard 애플리케이션을 이 패턴으로
리팩토링 함으로써 예시를 보여드리려고 합니다.
Jon Gossman 과
Dan Crevier 가
MVVM pattern in a WPF context 에 관해 블로깅 하였고 , 최근
Nikhil Kothari가 "
ViewModel Pattern in Silverlight using Behaviors" 란 제목의 훌륭한 포스팅을 올렸습니다. Martin Fowler 역시
the Presentation Model 이란 이름으로 이 패턴에 과한 글을 썼습니다. ViewModel이 실버라이트와 WPF에서 이렇게 관심을 끄는 이유는 바로 이 모델이 실버라이트와 WPF에서 지원하는 강력한 데이터 바인딩의 장점을 얻을 수 있게 해주게 때문입니다. MVVM 패턴의 중요개념중의 하나는 특정한 View (사용자 인터페이스)에 맞춘 Model을 만드는 것입니다. View-Model은
IsDiscountingEnabled나 PageTitle 같은 특별한 필드를 포함하고 있을 겁니다. 그리고 이 필드들은 도메인 모델에 깔려있는 하나이상의 필드 폼에 기반한 것입니다. 이 IsDiscountingEnabled 필드는 로그인한 사용자가 discount(할인)에 관한 권한을 가지고 있는지 없는지에 기반할 것이지만 실제 View 는 이것에 대해 알지도 못하며 관심도 없을 것입니다. View는 오직 View-Model의 IsDiscountingEnabled 필드에만 관심이 있고 도메인 모델(domain model)과는 동기화 되지 않습니다. 사용자가 Apply나 Save 버튼을 누르는 것 같이 특정 순간에 View-Model과 도메인 모델은 동기화가 발생하게 됩니다. 그리고 이 동안에 View와 View-Model은 아주 밀접하게 동기화(highly synchronized) 되게 됩니다. 어떻게 View와 View-Model의 동기화를 적용할 것인지는 사용하는 기술에 의존적이겠지만 Fowler는 Data Binding을 통해 이루어질 수 있다고 제안했습니다.
"아마도 Presentation 모델에서 가장 귀찮은 일은 Presentation Model과 View를 동기화하는 것일 것이다. 이것은 작성하기에는 간단한 코드지만 난 항상 이 반복적인 지루한 코딩을 최소화하고 싶어진다. 이상적으로 몇몇 종류의 프레임워크가 이런 일을 할 수 있지만 나는 닷넷의 데이터 바인딩과 같은 기술과 함께 나타나길 희망한다." - Martin Fowler -
Fowler가 말하고 있는 동기화 코드는 Name-TextBox에서 Person이라는 객체의 Name 프로퍼티로 Value를 전달하는코드를 말합니다. 저는 우리가 모두 이런 코드를 쓰고 있고 이것이 지루하고 반복적인 일이라는데 동의할 것이라고 확신합니다. 고맙게도 데이터 바인딩은 닷넷1.1과 윈도우 폼 때 부터 상당히 향상되어 왔고 WPF와 실버라이트에서는 데이터 바인딩이 동기화를 적용하기 위한 자연스런 선택이 되었습니다.
제가 이 패턴의 예제로 앞으로 사용할 애플리케이션은 저의
오스트리아 Remix 때 보여준 YouCard application 입니다. 리믹스에서 전 '디자이너를 위한 실버라이트2'에 대해 이야기 했었습니다. 이야기의 중심은 블랜드 2.5를 사용하여 애플리케이션을 디자인하고 만드는 것이었습니다. 애플리케이션의 핵심은 YouCardData 클래스와 데이터 바인딩 되어있는 YouCard UserControl 입니다. 이 클래스는 Twitter 와 Flickr로 부터 데이터를 받아들이고 애플리케이션의 Model과 View-Model로써 행동하는 기능들을 포함하고 있습니다. 이것은 또한 Twitter터로 부터 Http Request를 이용해 다운받은 Twitter-Feed에 관한 Tweet이나 Name, Bio 같은 View의 특수한 필드도 포함하고 있습니다. YouCardData 클래스는 일정간격으로 Twitter와 Flickr로 부터 데이터를 다운받는 타이머를 실행시킵니다. 단독책임(Single Responsibility) 같은 좋은 프로그래밍 원칙을 따라 저는 Twitter와 Flickr 기능들을 외부 클래스들로 분리 시킬려고 합니다. 이 클래스들은 Model이 될 것이고 Xml을 다운로드 받고 객체로 파싱하는 책임을 지게 될 것입니다. YouCardData는 ViewModel이 되고 Twitter 와 Flickr 서비스 이용을 위한 책임을 지고, View를 위한 로직과 UI로 부터 필요한 필드를 구성하게 될 것입니다. YourCardData의 본래 디자인이 데이터 바인딩을 위한 강력한 지원과 블래드2.5에서의 디자인 타임 지원을 강조하기 위해 구성되었기 때문에 리팩토링(refactoring) 작업을 시작하기 좋을 것 같습니다. 다음 그림은 현재 디자인(left)과 리팩토링 후의 원하는 디자인을 보여줍니다.
첫번째 작업은 외부 서비스를 위한 인터페이스를 정의하는 것입니다. 저는 ITwitter나 IFlickr 인터페이스를 정의하는 대신 IMicroBlog 나 IPhotoService같은 보다 일반적인 이름을 사용하기로 결정했습니다. 만약 우리가 FriendFeed나 Picasa, Windows Live Photo Gallery 같은 서비스를 지원한다고 할 때 이 편이 보다 합리적이라고 생각합니다. 그리고 현재의 코드를 각각의 인터페이스의 구체적인 구현(implementation)으로 리팩토링 할 것입니다. 하나는 실제 온라인 서비스를 위한 것이고 하나는 더미 데이터(역자주: 실제 서비스로 부터 불러오는 데이터가 아닌 직접 코딩, 하드 코딩을 통한 데이터)를 이용한 가짜 구현이 될 것입니다. 앞서 말했듯이 이 애플리케이션은 리믹스의 Creative 트랙을 위해 만들어진 데모이고 블랜드에서의 좋은 디자인 경험을 위해 만들어졌습니다. 때문에 디자이너가 실제 코드가 어떻게 작동하는지 알 수 있게 하기 위해 더미 데이터를 생성해야만 했습니다. 만약 XAML을 사용한 디자이너-개발자 워크플로우의 이점을 활용하려고 한다면, 디자인 툴에서 코드가 어떻게 작동할지 생각해 봐야 합니다. 현재 구현 에서는, 만약 생성자에서 블랜드에서 실행되는 것인지 아닌지를 체크하고 있다면 if-문을 통해서 YouCardData 클래스 안에 더미 데이터를 제공 할 수 있을 것입니다. 만약 블랜드 안에서 실행되고 있다면 YouCardData 클래스는 Twitter와 Flickr 서비스의 가짜 구현부분을 이용할 것이고 브라우저에서 실행되고 있다면 YouCardData안에 timer가 실행되고 실제 구현부분을 통해 Twitter와 Flickr를 통해 실제 데이터를 다운 받기 시작할 것입니다. 이것과 관련된 YouCardData 생성자의 중요한 부분은 다음과 같습니다.
리팩토링이 필요한 애플리케이션의 다음 조각은 메인 사용자 인터페이스(Main User Interface) 입니다. 내가 이해하는 바로는 Card에 관련된 모든 것들입니다. 현재 애플리케이션은 Twitter 사용자 이름을 넣을 TextBox와 Card를 새로 추가할 Add-Button 을 가지고 있습니다. Click-이벤트 핸들러 에서 새로운 YouCard 사용자 컨트롤의 Instance를 생성하고 StackPanel에 추가하게 됩니다. 그리고 YouCard 컨트롤에 의해 발생한 Close-Event를 후킹합니다. 만약 이벤트rk 발생하면 StackPanel로부터 컨트롤을 제거해주게 됩니다. 이 접근은 몇몇 안좋은 점이 있습니다. 첫번째 큰 문제는 View(Xaml페이지의 코드 비하인드)부분에 너무 많은 작동과 로직을 세워야 한다는 것입니다. 예를 들어 디자이너는 코드를 바꾸지 않고는 StackPanel을 FlowPanel로 바꿀 수 있는 방법이 없게됩니다. 그리고 또 많은 애플리케이션 로직이 특별한 UI-Control들과 UI-Event들과 연결되어 있기 때문에 단위 테스트(Unit Test)를 하기도 힘들어 집니다.
이런 문제를 해결 하기 위해 YouCardData 의 Observable Collection(역자주: Collection의 Item의 변화를 Event나 바인딩으로 감시할 수 잇는 Collection을 말합니다.) 을 포함한 Users라고 불리는 새로운 View-Model을 만듭니다. 이 Collection은 Items Presenter Control(역자주: ItemsControl을 말합니다.) 와 바인딩 되어있고 ItemsControl은 YouCard 사용자 컨트롤을 Data Template으로 사용하게 됩니다.
이제 두 UI가 View-Models와 바인딩 되는 경우를 통해 데이터를 보여주는 법을 다룰 수 있게 되었습니다. 그러나 이제 반 정도 왔을 뿐입니다. 우리는 사용자가 Card를 추가하고 삭제하는 것을 View-Models와 어떻게 상호작용(Interact) 할 것인지 알아야 합니다.
YouCard 애플리케이션에서는 View-Models에 영향을 주는 두개의 사용자 인터랙션이 있습니다. 새로운 사용자를 추가하는 TextBox와 Button, 그리고 List에서 카드를 제거하는 빨간 Button이 있습니다. 사용자는 Card를 추가하기 위해 사용자 이름을 입력하고 엔터를 칠 수도 있고 Add Button을 클릭할 수도 있습니다. Add Button은 사용자 이름이 올바른 값이 들어왔을 때만 활성화 되어야 합니다. 사용자가 Close 버튼을 클릭했을 때는 View-Model의 Collection으로 부터 item을 제거해 주어야 합니다. 이 경우 하나의 View에서 다른 View의 View-Model로의 의사소통이 섞여있습니다. YouCard 사용자 컨트롤은 메인 UI가 리스트로부터 자기자신을 삭제하도록 View-Model에게 알려주어야 합니다.
가장 확실한 방법은 Add 버튼에 클릭 이벤트 리스너(click event listner)를 추가해주고 View-Model에서 새로운 사용자를 추가하는 메소드를 불러주는 것입니다. 그리고 TextBox의 Text Changed 이벤트를 통해 입력된 값이 적당한 값인지 확인하고 텍스트에 기초한 TextBox가 되도록 활성화(Enable) 혹은 비활성화(Disable) 할 수 있을 것입니다. 그리고 빨간 Button에 Click Event를 받을 수도 있습니다. 메인 View는 이 이벤트를 받아서 View-Model로 부터 상응하는 카드를 제거할 수 있습니다. 이 솔루션의 문제는 역시 또 우리는 디자이너가 소유해야만 하는 View 부분에 로직과 작동들을 쌓아두어야 한다는 것입니다. 또한 이것 역시 유효 법칙(Validation Rules) 같은 Unit Test 등을 하거나 사용자의 추가나 삭제를 Local 머신의 독립적인 저장공간(isolated storage)에 남기는 것을 힘들게 합니다.
그래서 View와 View-Model 사이의 인터랙션 문제를 해결하기 위해 Command 패턴을 적용하려고 합니다. Command 패턴은 하나의 액션을 하나의 Command 객체로 캡슐화 시켜줍니다(encapsulate). Command 객체는 일반적으로 실행 메소드(excute method), 이름(name), 설명(description)과 Command가 실행가능한지에 대한
정보를 포함하고 있습니다. 하나의 Command가 여러개의 UIElement에 붙을 수 있습니다. 보통 버튼이나 단축키나 메뉴 아이템등을 통해 OpenFile-Command를 실행시키고 싶을 것입니다. WPF에는 Commands를 위한 이런 것들이 내장되어 있지만 실버라이트에는 포함되어 있지 않습니다.
Nikhil 은 Silverlight behaviors를 통해 Command와 같은 기능을 어떻게 가능하게 할 건지에 대한 좋은 아이디어를 소개하고 있습니다. 이것은 AJAX 작동을 위한 ASP.NET 컨틀롤 확장에서 사용되었던 것과 동일한 컨셉입니다. 그의 첫번째 ViewModel 포스트에서 그는 Xaml로 부터 Command를 실행시키기 위해 다음과 같은 코드를 사용하고 있습니다.
이 접근법의 근사한 점은 연결된 Click 이벤트에 어떤 Dynamic Language 도 쓸 수 있다는 것입니다. 이것은 페이지안의 다른 엘리먼트들의 파라미터를 얻거나 메소드를 실행시키는 것도 가능합니다. 이것은 View를 View-Model과 연결시키는데 놀랄만한 융통성을 보여 줍니다. 하지만 이 접근법의 문제점은 실버라이트 애프리케이션의 사이즈에 영향을 주는 Dynamic Language Runtime에 의존한다는 것이고, 이것보다 더 중요한 단점은 블랜드 2.5의 디자인 타임(desind time) 깨뜨린다는 데에 있습니다. 그래서 다른 대안이 필요했습니다.
저는 "Silverlight Extensions" project on CodePlex 에서 발견한 좀더 고전적인 Command 패턴을 사용하기로 했습니다. 프로젝트는 Controls와 Helper Class들 그리고 확장 메소드(extension methods)등을 포함하고 있습니다. 지금은 오직 커멘드 패턴 구현 부분에만 관심이 있기 때문에 이 클래스들을 YouCard 프로젝트로 옮기기로 합시다. 여기서 TextBox와 Button에 대한 Xaml 코드는 다음과 같습니다.
텍스트 박스는 View-Model에서 Username 프로퍼티와 바인딩되어 있습니다. 두 컨트롤 모두 View-Model의 Username을 파라미터로 받아 AddCard Command를 부릅니다.
커멘드들은 다음과 같이 정의 됩니다.
Static Command 클래스는 애플리케이션의 모든 Command들을 참조로 가지고 있습니다. 새로운 Command 객체가 생성될 때, Command 객체 안의 static Dictionary에 애플리케이션에 의해 만들어진 cashing된 Command 객체가 추가 됩니다. Xaml에서 CommandService Attribute를 사용할 때 CommandService 클래스는 CommandSubscription 클래스를 사용하여 적당한 Command를 UI 객체와 연결시켜주게 됩니다. 사용자가 AddUser-버튼을 클릭하거나 TextBox에서 enter를 쳤을 경우 CommandSubscription 클래스는 올바른 UI-event를 받게 되고 올바른 Command 객체의 Excuted event를 발생 시키게 됩니다. 이 커맨드가 실행되었을때 특정 액션을 하고 싶은 클래스는 간단히 Command 객체의 Excuted event만 연결해주면 됩니다. 여기선 View-Model 클래스에 카드를 추가하거나 삭제하는 것을 조정해주고자 합니다:
또한 AddUser 버튼의 활성, 비활성(enabling/disabling) 부분도 언급하는게 좋을 것 같습니다. 이것은 버튼의 IsEnabled 프로퍼티를 View-Model의 IsAddEnabled 프로퍼티와 데이터 바인딩 해줌으로써 구현할 수 있습니다. IsAddEnabled 프로퍼티는 다음과 같습니다.
TextBox는 IsAddEnabled 프로퍼티의 PropertyChanged 이벤트를 발생시킬 UserName 프로퍼티와 바인딩 되어있습니다. 프로퍼티를 얻는 곳(getter,역자주:프로퍼티의 get 부분)에 버튼이 활성화 되어야 하는지 아닌지 결정할 Validation rule 을 적용해주어야 합니다.
희망차게도 이 글은 어떻게 MVVM 패턴이 사용자 인터페이스와 분리하여 코드를 짤 수 있게 도와주는지에 대한 좋은 예를 보여줍니다. WPF와 실버라이트에서 지원하는 강력한 데이터 바인딩은 하드 코딩을 통해 View와 View-Model를 동기화 하는 것에 대한 걱정을 덜어줌으로써, 이 패턴을 특별히 흥미롭게 만들어줍니다. 커맨드들을 사용함으로써 구체적인 UI-Element의 이벤트로부터 애플리케이션의 액션들을 분리할 수 있습니다. 이것은 디자이너가 애플리케이션 네의 다양한 액션을 일으키는 UI 객체들을 자유롭게 선택할 수 있게 해줍니다. 무엇보다도 가장 좋은 점은 블랜드에서 지원하는 디자인 타임이 깨지지 않는 상태에서 이 모든 것이 가능하다는 것입니다. 다음 포스트에서는 Dependency Injection을 소개하면서 YouCard 애플리케이션을 좀더 테스트가 쉽게 향상 시킬 것입니다.
----------------------------------------------------------------------------------------------------------
역자 후기: 흠냐... 생각보다 포스트가 길어 힘들군요..^^;; 100% 모든 프로젝트에 적용될 수 있는 모델은 아니지만 꽤 이상적인 모델중에 하나인 것같습니다. 무엇보다도 Command 패턴은 복잡하고 불분명한 EventHandler로 부터 코드를 깔끔하게 지킬 수 있는 모델이라는 점에서 참 마음에 듭니다. MVVM 패턴도 친구 Gilber군이 이야기 한데로 Model을 다른 프로젝트로 분리시킬 경우 UI에 대한 독립성이 상당히 높아져서 멀티 플랫폼 환경이나 동일 서비스위의 다양한 애플리케이션 모델을 구축하는데 상당히 유리해 보입니다. 마지막으로 Interface와 더미데이터를 통한 디자이너를 배려한 구현은 개발자와 디자이너의 협업 관계에서 상당히 괜찮은 모델로 보입니다.
그럼 모두들 도움 많이 되셨길.. 그리고 좋은 포스트를 남겨준 원작자에게 감사를... Thank you for your great post.!!