태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

달력

06

« 2017/06 »

  •  
  •  
  •  
  •  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  •  

지난 번에 번역한 YouCard Re-visited : Implementing the ViewModel pattern이라는 글에 이어지는 YouCard Re-visited: Implementing Dependency Injection in Silverlight라는 글을 번역한 것이다.


이번 주에 올린 글에서 나는 YouCard 어플레케이션에서 Model-View-ViewModel 패턴을 구현한 방법에 대해서 썼다. 이번 글에서 나는 Inversion of Control 컨테이너와 Dependency Injection 패턴을 구현하는 방법에 대해 쓰려고 한다. 의존성 주입에 관한 글은 매우 많이 있으므로, 이 글에서는 Silverlight에서 의존성 주입을 사용하는 방법에 대해 집중하려고 한다. 특히, Ninject 프레임워크를 사용하는 방법에 대해 쓸 것이다. 이 패턴에 대해서 아주 잘 설명하고 있는 마틴 파울러의 에세이를 읽어보기를 강력하게 추천한다.

 

의존성 주입을 써보려고 할 때, 여러분은 이 패턴을 구현하고 있는 수많은 프레임워크들에 질려버릴지도 모른다. 그래서 마치 의존성 주입이라는 개념이 아주 크고 복잡한, 여러분이 소프트웨어를 만드는 방식에 엄청난 변화를 가져오는 개념이라고 생각하게 될지도 모른다. 큰 변화가 생기는 것이 사실일 수는 있다. 그러나 그것은 프레임워크를 사용하기 때문에 생기는 변화가 아니라 의존성 주입이라는 패턴을 도입함으로써 얻을 수 있는 유연성 때문에 생기는 변화일 것이다. 간단히 표현하자면 의존성 주입은 외부의 의존성을 어떻게 여러분의 어플리케이션 클래스에 제공할 것인가 하는 내용이다. 이전의 블로그 글에서 나는 Twitter와 Flickr 연동 부분을, 인터페이스를 구현하는 클래스들로 리팩터링했다. 코드가 Blend에서 실행되는지 웹 브라우저에서 실행되는지에 따라 실제 구현코드가 사용되기도 하고, 디자인 타임에 몇몇 더미 데이터를 제공하는 가짜(mock) 구현코드가 사용되기도 한다.

ninject-code1_749fbf00-74da-40f9-a91c-a5ee715c2b83.gif

위에 있는 코드의 문제점은 YouCardData 클래스와 IMicroBlog, IPhotoService 인터페이스를 구현하는 네 개의 클래스 사이에 결합 관계가 있다는 점이다. 뷰-모델(YouCardData)과 그것의 외부적인 의존물들(Twitter, Flickr) 사이에 있는 높은 결합 관계 때문에 코드의 유연성이 떨어진다. 인터페이스를 사용하도록 만들었기 때문에 Picasa 사진 서비스나 Tumblr 마이크로 블로그 서비스 같은 다른 서비스를 사용하는 기능을 구현할 수 있게 되기는 했다. 하지만 새롭게 구현한 서비스 클래스를 YouCardData 클래가 사용하도록 할 방법이 없다. 그리고 외부 서비스들을 가짜로 구현한 클래스를 제공할 수 없으므로 단위 테스트를 하기도 힘들다. 그래서 우리는 아래처럼 의존성 있는 개체를 생성자에 인자로 전달할 수 있도록 리팩터링할 수가 있다.

ninject-code2_13eb1037-d8c3-4fad-9b30-474da32f553f.gif

이렇게 함으로써 YouCardData를 사용하는 쪽에서는 생성자를 통해 의존성을 주입할 수 있게 된다. 이러한 방식을 '생성자를 이용한 의존성 주입' (constructor based dependency injection)이라고 부르는데, 여기서는 사용하는 코드 쪽에서 직접 의존성을 주입하는 식이다. 이렇게 변경함으로써, 다른 서비스 구현을 사용하도록 하거나, 단위 테스트를 하거나, 새로운 기능을 제공함에 있어서 아주 큰 유연성을 얻게 되었다. 하지만 이 코드에는 한 가지 문제가 있다. 데이터 컨텍스트를 생성하거나 하려면 XAML에서 이 클래스 인스턴스를 생성할 필요가 있는데, 이 때에는 파라미터가 없는 생성자가 필요하다. 아래와 같은 생성자를 하나 더 만들어서 이 문제를 해결할 수 있다.

ninject-code3_05e7555e-81a7-436b-9c08-4bf6b3c500d5.gif

이렇게 하여 XAML에서 선언적인(declarative) 방식으로 생성되는 인스턴스는 Twitter와 Flickr 서비스 구현을 사용하게 되고, 단위 테스트에서는 가짜 구현 개체를 사용할 수 있게 되었다. 하지만 여전히 완벽하지는 않다. 처음 만들었던 생성자에서는 YouCardData 클래스가 Blend에서 사용되고 있는 것인지 아닌지를 체크하도록 했었다. 현재 상태 그대로 유지할 수도 있겠지만 여기서 '제어 역전' (Inversion of Control) 컨테이너의 도움을 받아 보도록 하자.

 

Ninject

근래에 UI 프레임워크들과 패턴에 대해서 조사를 좀 해보았는데, 그들중 많은 수가 의존성 주입 프레임워크를 쓰고 있었다. Prism-AG 프레임워크Composite WPF 프레임워크를 Silverlight로 포팅한 것인데 DLLite라고 하는 작은 의존성 주입 프레임워크를 사용한다. Silverlight로 포팅된 또다른 의존성 주입 프레임워크에는 Unity가 있는데, Microsoft patterns and practices 그룹에서 내놓은 프레임워크 중 하나다. 하지만 내가 가장 좋아하는 것은 Ninject인데 의존성 주입 프레임워크 계의 닌자라고 할 수 있다. AltDotNet 팟캐스트의 5, 6번 에피소드에서 Nate Kohari의 이야기를 듣고 이 프레임워크에 관심을 가지게 되었다. Ninject는 스스로를 다음과 같이 설명하고 있다.

Ninject는 번개같이 빠르고, 엄청나게 가벼운 .NET 기반의 의존성 주입 프레임워크이다. 여러분의 어플리케이션을 느슷하게 결합된, 높은 응집도를 지닌 조각들로 나눈 후, 유연한 방법으로 다시 엮을 수 있는 방법을 제공해주는 프레임워크이다.  Ninject를 사용함으로써 여러분의 소프트웨어 아키텍처와 코드는 작성하기 쉽고, 재사용하기 쉬우며, 테스트하고 수정하기에 용이하게 될 것이다.

ninject-ninja1_56631962-4fcc-4af5-89a7-42765f316ce2.jpg

이 프레임워크의 컴파일된 크기는 110KB 밖에 되지 않고, Ninject.Core.dll만 참조에 추가하면 대부분의 사용 시나리오를 만족시킨다. Ninject의 강점은 XML이 아니라 코드로 설정을 한다는 점이다. 대부분의 IoC 컨테이너는 개체간의 의존성을 설정하는데 XML을 사용한다는 것을 강조하고 있다. 여러분의 소프트웨어 배포 환경이 설치할 때마다 많이 바뀌는 경우에는 XML을 사용하는 것이 이점으로 작용하지만, 많은 경우 아래와 같은 설정상의 부담이 따른다.

  1. 모든 개체 타입에 대해서 전체 어셈블리 이름(fully qualified assembly name)을 써야하기 때문에 설정 내용이 쓸데없이 많아지고 복잡하게 된다.
  2. 단순한 오타 때문에 어플리케이션이 제대로 동작하지 않게 되기 십상이다.
  3. 개체를 구성하는 규칙이 복잡해질수록 XML을 통해서 규칙을 표현한다는 것이 힘들어질 것이다.

그에 반해서 Ninject는 가끔은 "내장된 특수 분야 언어(embedded domain-specific language)"라고 불리기도 하는 늘어놓는 인터페이스(Fluent interface)를 사용한다. 이런 방식은 어플리케이션을 구성하는 데에 프로그래밍 언어의 힘을 빌릴 수 있다는 장점을 갖는다. 복잡하게 얽힌 클래스 간의 연결 관계를 표현할 수 있고, 사용자의 설정에 영향을 받는 클래스 연관 관계가 있을 경우 설정 파일을 읽어들여서 적용하는 방식을 방해하는 것도 전혀 없다.

... 코드를 이용해서 어플리케이션을 조립하는 편이 더 쉬운 경우가 있다. 배포할 때마다 별로 변하는 것이 없는 단순한 어플리케이션을 만드는 경우가 한 가지 예다. 이런 경우에는 별도의 XML 파일보다 몇 줄의 코드가 더 명확할 수 있다.

이와 대조적인 경우가, 조립 방법이 매우 복잡하고 조건에 따른 선택적인 과정이 섞여있는 경우이다. 이렇게 프로그래밍 언어와 비슷해지기 시작하면 XML은 무너지기 시작한다. 명료한 프로그램을 작성하기 위한 문법을 가진 실제 언어를 사용하는 편이 좋다.

... 이런 경우의 내 충고는, 항상 코드를 통해서 모든 설정을 할 수 있는 방법을 제공하고 나서, 분리된 설정 파일은 선택적인 기능으로 생각하라는 것이다. - 마틴 파울러

"… There are cases where it’s easier to use program code to do the assembly. One case is where you have a simple application that’s not got a lot of deployment variation. In this case a bit of code can be clearer than separate XML file.

A contrasting case is where the assembly is quite complex, involving conditional steps. Once you start getting close to programming language then XML starts breaking down and it’s better to use a real language that has all the syntax to write a clear program.

…My advice here is to always provide a way to do all configurations easily with a programmatic interface, and then treat separate configuration file as an optional feature. – Martin Fowler" (Inversion of Control Containers and the Dependency Injection pattern)

Ninject의 또다른 멋진 부분이 시작부터 Silverlight를 위해서 만들어졌다는 점이다. 즉, 원래 존재하던 더 큰 프레임워크를 싹둑 잘라서 Silverlight로 포팅한 것이 아니라는 말이다. 일단 Ninject는 작고 가볍다.

 

Dependency Injection with Ninject

Ninject를 사용하는 일은 아주 쉽다. 우선 우리가 해야할 일은 의존성 주입을 받는 생성자를 찾아서 간단한 어트리뷰트를 추가하는 일이다.

ninject-code4_07f0fc16-e98c-4ba5-bce8-3c45f0cb4159.gif

또, 화면에 보이는 모든 카드를 제어하는 메인 페이지의 뷰-모델에도 영속성 서비스를 위해서 의존성 주입을 사용하고 싶다.

ninject-code5_45dbc2c1-7873-4f65-bc1b-90099bdeab22.gif

Twitter 서비스를 구현한 클래스를 쓸 때에는 프록시 URL을 주입시키고 싶을 것이다. Twitter는 크로스도메인 접근을 허용하지 않기 때문에 우리가 운영하는 서버를 통해서 요청을 보내도록 해야한다.

ninject-code6_7099e6ad-f73c-48c9-8ae9-2904425bbcb6.gif

다음으로 우리가 해야할 일은 뷰-모델과 서비스들을 연결하는 작업이다. 많은 IoC 컨테이너에서 이 작업은 XML 설정을 통해 이뤄지지만 Ninject에서는 늘어놓은 인터페이스 스타일의 코드를 이용해서 작업이 이뤄진다. Ninject는 모듈이라는 개념을 사용해서 연결 작업을 수행한다. 하나의 커널(컨테이너)이 여러 모듈을 책임질 수 있다. YouCard 어플리케이션에서는 하나의 모듈이면 충분하다.

ninject-code7_f6257796-a955-47b3-b655-d42d4d74d7fe.gif

늘어놓는 인터페이스는 코드의 가독성을 높여준다. 그래서 굳이 위에 있는 클래스를 한 줄 한 줄 설명하는 데에 시간을 낭비할 필요는 없을 것 같다. 이 클래스는 StandardModule을 상속해서 Load 메서드를 재정의 하고 있다. Load 메서드에서는 서비스들과 그 서비스의 구체(concrete) 타입을 바인딩하고 있다. 바인딩 코드 한 줄 한 줄을 영어 문장이라고 생각하고 왼쪽에서 오른쪽으로 읽어보면 바인딩 규칙들이 아주 쉽게 이해된다. 또 이 코드가 Blend에서 실행되는 경우에 어떤 구현을 사용할 지 결정하는 부분에서 코드를 통한 설정 방식의 장점이 잘 드러나고 있음을 눈여겨 볼 필요가 있다. 웹 브라우저에서 실행되는 경우에는 Twitter 서비스를 사용하고 Blend에서 실행되는 경우에는 가짜로 구현된 서비스를 사용하도록 조건을 추가해두었다.

 

이 모듈을 사용하려면 간단히 Ninject 커널을 생성해서 모듈에 전달하면 된다.

ninject-code8_87031a87-c2c0-4cd3-a83c-63329429929f.gif

YouCardData 개체를 어떻게 생성할 지 걱정할 필요가 없다는 사실을 알아두기 바란다. Ninject 커널이 개체 생성을 모두 책임지고 YouCardModule에 정의된 바인딩 규칙에 따라 의존성을 주입해 준다.

 

Data binding to objects built by Ninject

여기까지 우리는 Ninject 커널을 설정했다. 다음 과제는 커널에서 얻은 뷰-모델 개체를 뷰에 넣어주는 것이다. 물론 Blend에서의 디자인 타임 경험을 유지하면서 말이다. 코드를 이용해서 데이터 컨텍스트를 세팅하는 것은 아주 간단하다. Application 클래스에서 커널을 생성하도록 하여 아래와 같은 일을 할 수 있다.

ninject-code9_1f612bd4-d89c-4f8b-bbb8-57e2cfc90072.gif

이렇게 하면 어플리케이션에 단 하나의 커널만 존재하게 할 수 있다. 하지만 여전히 어떻게 하면 선언적인 XAML 코드를 사용해서 Ninject가 생성해준 뷰-모델을 사용자 컨트롤의 데이터 컨텍스트에 설정할 수 있는가 하는 문제는 풀지 못했다. 며칠 간을 고민하고, 여러 가지 접근 방법을 시험해보고, Nate Kohari와 의논도 해본 후에 얻은 해답이 Service Locator였다. 이것은 IoC를 구현하는 또다른 패턴이다. 아마 여러분은 서비스 로케이터를 쓸 것인지 의존성 주입을 쓸 것인지 결정할 일이 자주 있을 것이다. 이 패턴에 대해 잘 설명하고 있는 마틴 파울러의 에세이 중 Service Locator vs Dependency Injection 부분을 읽어 보기를 추천한다.

 

서비스 로케이터를 간단히 설명하자면, 어플리케이션에서 사용하는 모든 서비스들의 참조를 가지고 있는 등록부라고 할 수 있다. YouCard에서라면 뷰-모델 같은 클래스는 IPhotoService나 ITwitterService의 구현체를 얻기 위해서 서비스 로케이터에 문의할 수 있다는 말이다. 서비스 로케이터와 의존성 주입 사이의 큰 차이는, 서비스 로케이터를 쓸 때는 여러분의 클래스가 구현체를 요청 하지만 의존성 주입을 쓸 때는 구현체를 클래스에 넣어준다는 점이다(pull vs. push). Ninject와 서비스 로케이터 패턴을 어떤 식으로 잘 섞어 쓸 수 있는지 Nate가 블로그에 글을 올린 것이 있다.

나는 서비스 로케이터와 의존성 주입 패턴이 서로 배타적이라고 생각하지 않는다. 사실 나는 Ninject 같은 의존성 주입 프레임워크와 서비스 로케이터 패턴을 같이 썼을 때 아주 효과적일 수 있다는 것을 발견했다. - Nate Kohari

 

YouCard에서 내가 만든 서비스 로케이터는 아주 간단하다.

ninject-code10_d7ea9906-616b-4de9-9965-81c3520711a9.gif

많은 경우 서비스 로케이터는 정적 클래스로 구현된다. 하지만 Silverlight 2에서는 정적 클래스에 대한 데이터 바인딩은 지원되지 않기 때문에 그 방식은 쓸 수 없다. 그래서 정적 생성자를 만들고, 사적인 정적 kernel 필드를 만드는 방식으로 처리했다. 정적 생성자는 다른 모든 생성자들 보다 먼저 클래스에서 가장 처음 실행된다. 정적 생성자에서는 커널이 있는지 확인해서 없으면 우리가 바인딩 설정을 하면서 만든 모듈을 사용하는 커널 인스턴스를 만든다. 커널 필드를 정적으로 만들었기 때문에, 어플리케이션에 커널 인스턴스가 하나만 존재함을 보장받을 수 있다. 클래스에는 뷰-모델을 커널로부터 얻어내는 두 개의 인스턴스 속성과, 모든 타입의 개체를 컨테이너로부터 얻을 수 있는 제네릭 인스턴스 메서드가 있다.

 

의존성 주입 개념을 도입하기 전에는 뷰와 뷰-모델을 이런 식으로 연결했다.

ninject-code11_7bcce970-b825-4ac6-8b78-13bf2cca1b85.gif

서비스 로케이터를 도입한 후에는 몇 가지 바꿀 것이 있다. 첫 번째로는 서비스 로케이터를 어플리케이션 수준의 리소스로 생성시킨다.

ninject-code12_b6ac62b6-557c-443d-aee3-410457b139a3.gif

그 후에 각각의 뷰에서 데이터 컨텍스트를 얻는 방법을 변경한다.

ninject-code13_d2368669-5217-4421-a6e3-be171a1ae90e.gif

각각의 뷰는 해당하는 뷰-모델을 서비스 로케이터의 속성에 데이터 바인딩하여 얻는다. (역자 첨언 : 위에서 YouCardViewModel은 더 위의 ServiceLocator 클래스 구현에서는 YouCardData라고 정의되어 있으므로, YouCardData라고 쓰는 게 맞는 것 같다.) 이 접근 방식의 가장 멋진 부분은, Blend의 디자인 타임을 완벽하게 지원하고 있다는 것과 디자인 타임에 가짜 구현 서비스를 통해 샘플 데이터를 제공한다는 것이다.

 

Conclusion

Silverlight에서 의존성 주입을 사용하는 것은 다른 .NET 어플리케이션에 쓰는 것보다 절대 어렵지 않다. 프레임워크를 가볍고 빠르게 유지하는 데 중점적인 노력을 쏟고 있다는 점 때문에 Ninject는 Silverlight에 잘 어울린다. 그러나 이렇게 간단한 샘플 프로젝트에서 Ninject를 사용하는 것은 쓸데없는 짓이라고 생각하는 사람도 있을 것이다. 의존성 주입 코드를 서비스 로케이터 안에 직접 작성할 수가 있고, 그렇게 해서 Ninject에 대한 의존성을 없애면서도 여전히 유연성은 유지할 수 있다. 반면, Ninject는 110KB (XAP 파일에 넣어서 압축하기 전 크기) 밖에 안되어서 어플리케이션 크기에 별로 영향을 주지 않는다. 100KB면 이 블로그 글에 사용한 이미지 크기의 절반도 되지 않는다.

ninject-ninja2_1c42192e-53fd-4a9d-b74d-e1ceda6e9dd0.jpg

의존성 주입을 사용함으로써 여러분은 코드를 더 유연하고 테스트하기 쉽게 유지할 수 있다. Silverlight 어플리케이션에서는 특히 이런 점들이 중요하다. 여러분이 Silverlight의 강력함을 알아감에 따라 UI 요구사항도 많이 바뀌어 갈 것이기 때문이다. 나는 Blend를 사용하는 디자이너에게 최상의 경험을 제공하는 것이 중요하다고 확신하고 있고, 의존성 주입이 그것을 바꿀 이유는 없다.

이 글은 스프링노트에서 작성되었습니다.

신고
Posted by wafe

댓글을 달아 주세요

Silverlight Model-View-ViewModel Pattern 1에 이어서.



눈치챘을지도 모르겠지만, 이 코드는 의존성 주입(dependency injection)의 필요성을 외치고 있고, 내가 다음 블로그 포스트에서 다루려고 하는 게 바로 그것이다. 우리는 현재 어플리케이션 개체의 타입을 체크한다. Blend는 그것 스스로의 어플리케이션 개체를 제공하는 반면, 실제로 실행되는 어플리케이션은 우리가 만든 어플리케이션 개체를 우리에게 준다.

리팩토링이 필요한 다음 부분은 메인 사용자 인터페이스이다. 여기서 내가 말하는 것은 카드 주위의 모든 것이다. 어플리케이션은 Twitter 계정을 넣는 텍스트 상자 하나와 새 카드를 추가하기 위한 추가 버튼을 가지고 있다. 클릭 이벤트 핸들러에서 우리는 새 YouCard 사용자 컨트롤의 인스턴스를 만들어서 스택 패널에 넣는다. 또한 YouCard 컨트롤에서 발생하는 클로즈 이벤트를 리스닝하는 이벤트 핸들러를 작성한다. 이런 접근 방법은 몇 가지 이유 때문에 좋지 않은 방법이다. 주요한 문제는 너무 많은 행동(behavior)과 로직을 뷰(XAML 페이지의 코드 비하인드)에 넣는 것이다. 예를들어 디자이너는 코드를 수정하지 않고서는 스택 패널에서 플로우 패널로 바꿀 수가 없다. 또한 어플리케이션 로직의 너무 많은 부분이 특정 UI 컨트롤과 UI 이벤트에 종속되어 있어 코드를 유닛 테스트하기가 어렵다.

이런 문제를 해결하기 위해서 우리는 YouCardData 개체의 옵저버블 컬렉션(Users)을 포함하는 새로운 뷰-모델을 만들 것이다. 이 컬렉션은 YouCard 사용자 컨트롤을 데이터 템플릿으로 사용하는 items presenter control에 바운드된다.


이제 우리는 뷰-모델에 바운드된 UI들을 가지고, 데이터를 어떻게 표시할 것이지 콘트롤하게 되었다. 하지만 이것은 아직 절반 정도밖에 안된다. 아직 사용자가 카드를 추가/삭제할 때 뷰-모델과 인터랙션하게 할 방법이 필요하다.

YouCard 어플리케이션에는 뷰-모델에 영향을 주는 인터랙션 포인트가 두 군데 있는데, 텍스트 상자와 새 사용자 추가 버튼, 목록에서 카드를 제거하는 빨간 버튼이 그것이다. 사용자는 사용자 이름을 입력하고 엔터 키를 누르거나, 추가 버튼을 클릭하여 Users 컬렉션에 새 아이템을 추가한다. 추가 버튼은 입력된 사용자 이름이 올바른 경우에만 활성화되어야 한다. 사용자가 카드에 있는 닫기 버튼을 클릭하면 우리는 뷰-모델에 있는 컬렉션으로부터 그 아이템을 제거해야 한다.

가장 뻔한 해답은 추가 버튼에 클릭 이벤트 리스너를 추가해주고, 새 사용자를 추가하기 위해서 뷰-모델에 있는 메서드를 호출하는 방법일 것이다. 우리는 텍스트 상자의 text changed 이벤트를 받아서 입력을 검증하고 텍스트에 따라서 텍스트 상자를 활성화/비활성화 할 수 있을 것이다. 또한 우리는 빨간 버튼을 클릭했을 때 발생하는 이벤트를 YouCard 컨트롤에 추가할 수도 있을 것이다. 메인 뷰는 이 이벤트를 받아서 해당하는 카드를 뷰-모델로부터 제거할 수 있을 것이다. 이 방법의 문제점은 디자이너가 소유해야만 하는 뷰에 또 다시 로직과 행동(behavior)을 넣게 된다는 것이다. 그렇게 되면 검증 규칙 같은 것들과 추가되거나 제거된 사용자를 로컬 컴퓨터의 isolated storage에 저장하는 것(우리 어플리케이션은 당신이 추가한 카드들을 저장한다) 같은 일들을 유닛 테스트하기가 더 힘들어진다.

뷰와 뷰-모델 간의 인터랙션 문제를 해결하기 위해서 우리는 커맨드 패턴을 적용할 것이다. 커맨드 패턴을 사용하면 액션을 커맨드 개체로 캡슐화할 수 있게 된다. 커맨드 개체는 보통 실행 메서드, 이름과 설명, 커맨드가 활성화되었는지 아닌지에 대한 몇몇 정보를 포함한다. 하나의 커맨드 개체가 여러 UI 엘리먼트에 붙을 수 있다. 당신은 버튼, 키보드 단축키, 메뉴 아이템으로부터 open-file-command를 호출하고 싶을 지도 모르겠다. WPF는 Commands에 대한 지원을 내장하고 있지만 Silverlight 2에는 포함되지 않았다.

Nikhil은 Silverlight behavior를 통해서 어떻게 커맨드 같은 기능을 만들 수 있는지에 대해서 멋진 아이디어를 소개했다. 그것은 ASP.NET 컨트롤을 AJAX behavior로 확장하는 데 사용된 것과 같은 개념이다. 첫번째 ViewModel 포스트에서 Nikhil은 XAML에서 커맨드를 호출하는 데에 다음과 같은 문법을 사용했다.


첨부 속성(attached property)를 이용해서, 뷰-모델에 있는 Search 메서드에 검색 텍스트 상자의 text 속성을 인자로 전달하면서 메서드를 호출하는 행동을 버튼에 추가했다. 이어지는 포스트에서 Nikhil은 Dynamic Language Runtime을 사용하여 더욱 컴팩트한 문법을 사용하는 방법을 보여주었다.

이 접근 방법의 멋진 점은 첨부된 클릭 이벤트에 모든 동적 언어 표현을 사용할 수 있다는 점이다. 메서드 호출이나 페이지에 있는 다른 엘리먼트로부터 파라미터를 얻는 것 등을 사용할 수 있는 것이다. 이 방법으로 뷰를 뷰-모델에 연결하게 되면 놀라운 유연성을 얻게 된다. 이 방법의 문제점은 동적 언어 런타임에 의존성이 생겨서 Silverlight 어플리케이션 크기가 늘어나게 된다는 것이다. 하지만 내가 더 중요하게 생각하는 부분은 이 방법을 쓰게 되면 Blend 2.5에서 제공하는 디자인 타임 지원을 사용할 수 없다는 점이다. 따라서 우리는 다른 접근법을 찾아야 한다.

나는 CodePlex에 있는 "Silverlight Extensions" 프로젝트에서 발견한 좀더 고전적인 커맨드 패턴 구현을 사용하기로 했다. 그 프로젝트는 컨트롤, 헬퍼 클래스, 확장 메서드 등을 포함하고 있다. 우리는 커맨드 패턴 구현에만 관심이 있으므로, 구현 클래스들을 YouCard 프로젝트로 옮기기로 했다. 이제 텍스트 상자와 버튼의 XAML 코드는 아래와 같다.

텍스트 상자는 뷰-모델의 Username 속성에 바운드된다. 두 컨트롤은 모두 뷰-모델의 Username을 파라미터로 사용하여 AddCard 커맨드를 호출한다.

커맨드는 다음과 같이 정의된다.

정적 Commands 클래스는 우리 어플리케이션에서 사용할 수 있는 모든 커맨드에 대한 참조를 유지한다. 새 Command 개체 인스턴스를 만들 때 그것은 Command 개체에 있는 정적 사전(static dictionary)에 추가된다. 어플리케이션에서 만들어진 모든 커맨드 개체는 그 사전에 캐싱된다. XAML에서 CommandService attribute를 사용할 때 CommandService 클래스는 CommandSubscription 클래스를 사용하여 올바른 커맨드를 가진 UI 엘리먼트에 연결된다. 사용자가 사용자 추가 버튼을 클릭했을 때 혹은 텍스트 상자에서 엔터 키를 입력했을 때, CommandSubscription 클래스는 UI 이벤트를 받아서 해당하는 Command 개체의 Executed 이벤트를 발생시킨다. 커맨드가 실행되었을 때 무언가 행동을 취하기를 원하는 클래스는 단순히 Command 개체의 Executed 이벤트를 받기만 하면 된다. 지금같은 경우 우리는 뷰-모델 클래스에서 카드를 추가하고 삭제하는 작업을 처리하기를 원한다.

얘기할만한 가치가 있는 다른 부분으로는 사용자 추가 버튼을 활성화/비활성화하는 부분이 있다. 이 부분은 IsEnabled 버튼의 속성을 뷰-모델의 IsAddEnabled 속성에 데이터 바인딩하는 식으로 되어있다. IsAddEnabled 속성은 이렇게 되어있다.

텍스트 상자는 IsAddEnabled 속성에 대한 PropertyChanged 이벤트를 발생시키는 Username 속성에 바운드되어 있다. 속성의 get 부분에서 우리는 버튼이 활성화되어야 하는지 아닌지에 대한 검증 규칙을 적용한다.

이 포스트가 Model-View-ViewModel 패턴이 어떻게 UI로부터 가능한한 많은 코드를 분리해내는지 당신에게 알려주는 또다른 예제가 되기를 바란다. WPF와 Silverlight의 강력한 데이터 바인딩 지원은 이 패턴을 아주 흥미롭게 만들어준다. 뷰와 뷰-모델 사이의 동기화 코드를 직접 작성하는 부분에 대한 걱정을 하지 않아도 되기 때문이다. 커맨드 패턴을 사용하면 어플리케이션의 액션을 UI 엘리먼트로부터 분리할 수 있다. 이로써 디자이너는 여러가지 액션을 호출하는 데 사용하는 UI 엘리먼트를 선택할 수 있는 자유를 얻는다. 그리고 무엇보다도, 우리는 Blend에서의 디자인 타임 지원을 그대로 유지하면서 이 모든 것을 해냈다. 다음 포스트에서는 의존성 주입(dependency injection)을 도입함으로써 YouCard 어플리케이션의 테스트 가능성을 높이는 작업을 계속할 것이다.


실컷 열심히 번역을 하고 보니 뒷북이란 걸 알게됐다. ㅜ.ㅜ 이미 실버라이트 카페의 boxmile 님께서 번역을 해두셨네. 다음에는 이런 삽질을 하지 말도록 하자.

신고
Posted by wafe

댓글을 달아 주세요

  1. 네오군 2009.01.08 21:54 신고  댓글주소  수정/삭제  댓글쓰기

    헉!!!!!!!!!!!!!!! 저두 님 글 보구 알아챘는데여... 정말 제가 죽고 싶은건.. 님께서 링크해주신 번역글을 제가 전에 읽어봤다는 겁니다.. 그걸 까먹고 있었어여 ㅠㅠ;; 늙으면 죽어야지 ㅠ;

Silverlight의 MVVM 패턴을 소개하고 있는 YouCard Re-visited: Implementing the ViewModel pattern 라는 글을 번역한 글이다. 아직 1/3 정도라서 틈나는 대로 이어서 번역하려고 한다. 실력 부족/시간 부족으로 딱히 좋은 번역이라고 하긴 힘들겠지만.


Model-View-Control (MVC) 패턴은 더 이상 유명할 수가 없을 정도이다. ASP.NET MVC 프레임워크로 Microsoft도 이제 그 대열에 뛰어들었다. Ruby on Rails, Django (Python), Spring MVC (JAVA) 같은 다른 유명한 프레임워크들은 모두 이 유명한 패턴을 구현한 것이다. MVC 패턴은 요청-응답 기반이라는 웹의 본성에 아주 잘 들어맞는다. 요청이 들어오면, controller가 무엇을 해야하는지 결정하여, model에게 알려준다. 그리고 view 엔진에게 렌더링을 시킨다.

반면 Silverlight는 비록 Silverlight가 전통적인 웹 어플리케이션 속에서 실행되기는 하지만, 웹 어플리케이션 보다는 Windows Presentation Foundation 같은 리치 클라이언트 어플리케이션 프레임워크와 공통점이 더 많다. 그러므로 어플리케이션을 설계할 때 다르게 생각할 필요가 있다. 이 포스트에서 나는 View-Model-ViewModel (MVVM) 패턴, 혹은 마틴 파울러가 말한대로 Presentation Model에 대해서 얘기할 것이다. 예제로서 나는 내 YouCard 어플리케이션을 리팩터링하여 이 패턴을 쓰도록 만들 것이다.

Jon GossmanDan CrevierWPF에서의 MVVM 패턴에 대해서 포스팅을 했고, 최근 Nikhil KothariViewModel Pattern in Silverlight using Behaviors라는 제목의 훌륭한 글을 썼다. 마틴 파울러(Martin Fowler)도 이 패턴에 대해서 썼지만 Presentation Model이라는 다른 이름을 사용했다. ViewModel을 Silverlight와 WPF 어플리케이션에서 사용하는 것이 꽤 재미있는 이유는, 이 패턴이 Silverlight와 WPF의 강력한 데이터 바인딩의 장점을 최대한 살려주기 때문이다. MVVM 패턴의 한 가지 중요 컨셉은 특정한 뷰 (사용자 인터페이스)에 딱 맞춘 특정 모델을 정의한다는 것이다. 그 뷰-모델은 당신의 도메인 모델을 형성하는 하나 혹은 그 이상의 필드에 기반한 IsDiscountingEnabled, PageTitle 같은 특별한 필드를 포함할 수도 있다. 당신의 IsDiscoundEnabled 필드는 할인(discount) 권한을 가진 그룹에 소속된 사용자인지 아닌지를 나타내는 것일지도 모른다. 그러나 뷰는 이런 사실을 모를 뿐더러 관심도 없다. 뷰는 뷰-모델의 IsDiscountEnabled 필드에만 신경을 쓰고, 도메인 모델과는 커플링되지 않는다. 뷰와 뷰-모델은 높은 수준으로 동기화된다. 반면, 뷰-모델과 도메인 모델 사이의 동기화는 단지 특정 포인트에서만(사용자가 Apply나 Save 버튼을 클릭했을 때 같은 경우에만) 일어날 뿐이다. 당신이 뷰와 뷰-모델 사이의 동기화를 어떻게 구현할 것인지는 당신이 사용하는 기술에 달려있다. 그러나 마틴 파울러조차도 그 작업이 데이터 바인딩을 통해서 수행되는 것을 추천했다.

아마도 Presentation Model에서 가장 귀찮은 부분은 Presentation Model과 뷰의 동기화이다. 작성하기에 단순한 코드이기는 하지만, 나는 이렇게 지루하게 반복되는 코드를 최소화하는 것이 좋다. Ideally some kind of framework could handle this, which I'm hoping will happen someday with technologies like .NET's data binding.(막 해석 : 이론적으로는 몇몇 프레임워크가 이런 일을 할 수 있지만, 내가 바라는 것은 닷넷의 데이터 바인딩과 같은 기술과 함께 나타날 것이다.) – Martin Fowler

마틴 파울러가 말하는 동기화 코드는, name 텍스트 상자의 값을 Preson 개체의 name 속성으로 옮기는 코드 같은 것이다. 모든 개발자들은 이런 코드들을 작성한 경험이 있고, 우리 모두 그것이 지겹고 반복적인 작업이라는 점에 동의할 것이라고 나는 확신한다! 고맙게도 데이터 바인딩은 .NET 1.1 과 Windows Forms 때부터 많이 개선되었고, 동기화 코드를 작성할 때 WPF와 Silverlight 데이터 바인딩을 선택하는 것은 자연스러운 일이다.


내가 예제로 사용할 어플리케이션은 내가 호주 REMIX에서 만든 YouCard 이다. REMIX에서 나는 디자이너를 위한 Silverlight 2에 대해서 이야기했다. 이야기의 초점은 어플리케이션을 빌드하고 디자인하기 위해서 Blend 2.5를 사용하는 방법에 대한 것이었다. 어플리케이션의 코어는 YouCard 사용자 컨트롤이었다. 그 컨트롤은 YouCardData 클래스에 데이터 바운딩되는 것이다. YouCardData 클래스는 Twitter와 Flickr에서 데이터를 받아오는 기능을 갖고 있고, 어플리케이션에서 모델과 뷰-모델의 역할을 한다. 이 클래스는 Tweet, Name, Bio같이 뷰에 관련된 필드를 갖고 있는데, 이 정보들은 HTTP 요청을 사용해서 Twitter로부터 얻은 Twitter-feed에서 추출한 것이다. YouCardData 클래스는 주어진 시간 간격으로 트위터와 플리커로부터 데이터를 다운로드하는 타이머를 시작시킨다. 단일 책임 원칙 같은 좋은 프로그래밍 원칙을 따르기 위해서 나는 트위터와 플리커 관련 기능을 외부 클래스로 분리시킬 것이다. 이 클래스들이 우리의 Model이 될 것이다. 이 클래스의 책임은 다운로드하고 XML을 분석해서 개체를 만드는 일이 될 것이다. YouCardData 클래스는 우리의 ViewModel이 될 것이고, 트위터와 플리커 서비스를 View의 논리에 맞게 사용하는 책임을 맞게 된다. 또한 UI에 필요한 필드들을 노출시키는 책임도 지게 된다. YouCardData 클래스는 원래 Blend 2.5가 제공하는 강력한 데이터 바인딩과 디자인 타임 지원을 강조하기 위해 설계되었기 때문에, 우리는 리팩토링을 위한 좋은 시작점을 가지고 있는 셈이다. 아래 도표의 왼쪽은 현재의 설계이고, 오른쪽은 리팩토링 후 기대하고 있는 디자인이다.


처음으로 해야 할 일은 외부 서비스를 위한 몇 가지 인터페이스를 정의하는 일이다. ITwitterIFlickr 인터페이스를 만드는대신, 나는 IMicroBlog와 IPhotoService 라는 좀 더 일반적인 이름을 사용하기로 했다. 이 결정은 우리가 FriendFeed, Picasa 또는 Windows Live Photo Gallery를 지원하려고 한다면 좀 더 의미있는 결정일 것이다. 나는 원래의 코드를 리팩토링하여 각각의 인터페이스를 구현하는 두 개의 구상 클래스로 만드려고한다. 하나는 진짜 온라인 서비스를 위한 것이고, 하나는 더미 데이터를 가진 목(mock) 구현이다. 앞에서 언급한대로 이 어플리케이션은 REMIX의 크리에이티브 트랙을 위한 데모로서 작성되었고, Blend에서의 멋진 디자인 타임 사용자 경험을 제공하기 위해서 작성되었기 때문에, 우리는 코드가 디자이너에 의해 사용될 때를 위해 더미 데이터를 생성할 필요가 있다. 만약 당신이 XAML이 가능하게 해주는 디자이너-개발자 업무 흐름으로부터 이익을 얻을 생각이라면, 당신의 코드가 디자인 도구에서 어떻게 동작할 지 생각해보아야 한다. 현재 구현에서 더미 데이터는 YouCardData 클래스 생성자에서 Blend에서 실행되는 것인지 아닌지 체크하는 if 문을 통해 제공된다. 만약 YouCardData 클래스가 Blend에서 실행되면 Twitter와 Flickr 서비스의 목 구현이 사용된다. 브라우저 안에서 실행되면 타이머를 시작시키고 Twitter와 Flickr에서 데이터를 받아오기 시작하는 실제 구현이 사용된다. YouCardData 클래스 생성자의 중요한 부분은 이제 이렇게 보일 것이다.



[Translation] Silverlight Model-View-ViewModel Pattern 2에서 계속.



신고
Posted by wafe

댓글을 달아 주세요

  1. 네오군 2009.01.08 21:53 신고  댓글주소  수정/삭제  댓글쓰기

    노고에 진심으로 감사드립니다. 앞으로도 부탁드려용 ^^;