블로그 이미지
Sunny's

calendar

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

Notice

'Pattern'에 해당되는 글 3

  1. 2010.12.23 Proactor pattern
  2. 2010.08.24 Head First Design Patterns 정리
  3. 2009.03.11 ViewModel 패턴의 적용(Implementing)
2010. 12. 23. 17:22 Pattern

출처

원문 : http://www.cse.wustl.edu/~schmidt/PDF/proactor.pdf

번역문 : http://www.redwiki.net/wiki/wiki.php/Proactor

 

개요

현대의 운영체계들은 동시실행에 기초한 어플리케이션(서버)를 개발하는 것을 지원하기 위해 여러가지 매커니즘들을 제공한다. 동기적 멀티쓰레딩은 여러 동작이 동시다발적으로 실행되는 어플리케이션을 개발하는데 있어서 인기있는 매커니즘이다. 그렇지만 쓰레드는 종종 높은 성능 과부하를 가질 때가 있고, 동기화 패턴과 법칙들에 대한 깊은 지식을 요구한다. 그러므로, 여러 운영체계들은 동시실행 처리에 있어서 멀티쓰레딩의 과부하나 복잡도의 대부분을 완화시키는 장점이 있는 비동기적 매커니즘을 지원한다.

이 논문에서 제시하는 proactor 패턴은 운영체계에 의해 제공되는 비동기 매커니즘들을 효율적으로 다루는 어플리케이션과 시스템을 구축하는 법에 대해서 설명하고 있다. 어플리케이션이 비동기적인 작업을 수행할 때, 운영체계는 어플리케이션을 대신하여 작업을 실행한다. 이것은 어플리케이션으로 하여금 필요한 만큼의 수의 쓰레드를 가질 필요없이 동시에 동작하는 다수의 동작을 실행할 수 있게 해준다. 따라서, proactor 패턴은 서버 프로그래밍을 단순화 시켜주며, 비동기 처리를 위한 운영체계의 지원에 의존하고 보다 적은 쓰레드를 요구하게 됨으로써 성능을 증진시켜주게 된다.

 

취지

proactor 패턴은 비동기 이벤트들의 완료시점에 실행되는 다중 이벤트 핸들러들의 디스패칭과 디멀티플랙싱을 지원한다. 이 패턴은 완료 이벤트들의 디멀티플랙싱을 통합하고 그에 알맞은 이벤트 핸들러들을 디스패칭하는 것을 지원함으로써 비동기기반의 어플리케이션 개발을 단순화해준다.

 

동기

이 섹션은 proactor 패턴을 사용하기 위한 개념과 동기를 기술한다.

 

고성능 서버의 의미과 능력

동기적인 방식의 멀티쓰레드 혹은 반응적인(reactive) 프로그래밍상에서 제약없이 동시처리방식으로 작업들을 실행시키려 하는데 성능상의 잇점이 요구될 때에는 proactor 패턴을 사용한다. 이 잇점들을 설명하기 위해서 동시처리방식으로 다중의 처리를 실행할 필요가 있는 네트워크 어플리케이션을 고려하자. 예를 들자면, 고성능의 웹서버는 반드시 여러개의 클라이언트 [1, 2]로부터 보내어진 HTTP 요청들을 동시에 처리해야만 한다. "Figure 1"은 웹브라우져들과 웹서버사이의 전형적인 상호작용관계를 보여준다. 사용자가 브라우져에게 URL을 열도록 지시하면, 브라우져는 HTTP GET 요청을 웹 서버로 보낸다. 요청을 접수하면 서버는 요청을 파싱하고 적법한지 검사한 후, 지정된 화일(들)을 브라우져로 되돌려 보낸다.

figure1.PNG

Figure 1. 일반적인 웹서버의 구조

 

고성능의 웹서버가 개발되기 위해서는 다음 능력들을 가져야한다:

  • 동시처리(Concurrency); 서버는 동시에 여러개의 클라이언트 요청을 수행해야만 한다.
  • 효율성(Efficiency); 서버는 지연(latency)을 최소화해야하고, 대역폭을 최대로 활용해야 하며, 불필요하게 CPU(들)을 동작시키는 것을 피해야한다.
  • 프로그래밍 단순화(Programming simplicity); 서버의 디자인은 효율적인 동시처리에 대한 운영 전략의 적용을 단순화할 수 있어야 한다.
  • 적응성(Adaptability); 신규 혹은 개선된 트랜스포트 프로토콜(HTTP 1.1과 같은)을 지원하는데 있어서 최소한의 관리 비용이 들도록 해야한다.

 

웹서버는 몇가지 동시처리 전략(다중 동기화된 쓰레드방식, reactive한 동기적 이벤트 디스패칭, proactive한 비동기 이벤트 디스패칭)들을 사용하여 구현될 수 있다. 아래에서 우리는 전통적인 접근 방식의 결점을 찾아보고 어떻게 proactor 패턴이 고성능의 서버 어플리케이션에 대한 효율적이고 유연한 비동기 이벤트 디스패칭 전략을 지원하는 강력한 태크닉을 제공하는지 살펴볼 것이다.

 

전통적인 동시처리방식 서버모델들의 일반적인 덫과 함정들

동기화된 멀티쓰레딩과 reactive한 프로그래밍은 동시처리를 구현하는 일반적인 방법이다. 이 장은 이 프로그래밍 모델들에 대한 결점들을 설명한다.

 

다중 동기화 쓰레드를 통한 동시처리

아마도 대부분의 동시처리방식의 웹서버를 구현하는 직관적인 방법은 동기적인 멀티쓰레딩 방식이다. 이 모델에서는 다중 서버 쓰레드들이 동시에 여러 클라이언트로 부터 HTTP GET 요청을 처리한다. 각 쓰레드는 접속 구축을 실행하고, HTTP 요청을 읽고, 요청을 파싱하며, 화일 전송 처리를 동기적으로 수행한다. 결과적으로 각 처리는 해당 처리가 완료될 때 까지 블럭당한다.

 

동기화된 쓰레딩 방식의 주된 잇점은 어플리케이션 코드의 단순함이다. 특별한 경우, 클라이언트 A의 요청을 서비스하기위해 웹서버에 의해 실행되는 처리들은 클라이언트 B의 요청을 서비스하기 위한 처리와는 대부분 독립적이다. 따라서, 쓰레드간에 공유되는 상태들의 양이 적기 때문에 별도의 쓰레드상에서 서로 다른 요청들을 서비스하는 것이 쉬운 것이다. (이것은 동기화의 필요성을 최소화하는 요인이다) 게다가, 별도의 쓰레드상에서 어플리케이션 로직을 실행하는 것은 개발자로 하여금 순차적인 명령들과 블록킹 처리를 다루는 것을 허용한다.

figure2.PNG

Figure 2. 멀티스레드 웹서버 구조


"Figure 2"는 어떻게 동기적인 쓰레드를 사용하여 디자인된 웹서버가 여러개의 클라이언트들을 동시실행방식으로 처리할 수 있는지 보여준다. 이 figure는 Sync Acceptor가 동기적으로 네트워크 접속을 accept 처리하기위한 서버측 구조를 은폐하고(encapsulate)있다는 것을 보여준다. "연결 1개당 쓰레드 1개" 방식을 사용해서 HTTP GET 요청을 서비스하기위한 각각의 쓰레드들의 실행단계는 다음과 같이 요약될 수 있다:

  1. 각 쓰레드는 accept()함수실행시 클라이언트 접속요청이 올때까지 동기적으로 블록당한다.
  2. 클라이언트가 서버에 연결되면, 접속이 accept된다. (블럭이 풀린다)
  3. 새로 접속된 클라이언트의 HTTP 요청이 동기적으로 네트워크 연결을 통하여 읽혀진다.
  4. 요청을 파싱한다.
  5. 요청된 화일을 동기적으로 읽는다.
  6. 화일의 내용이 동기적으로 클라이언트에게 전송된다.

     

동기적 쓰레드 모델을 적용한 웹서버를 C++ 코드 예제를 부록 A.1에 첨부해놓았다. 앞에서 기술했던 것처럼, 각각에 연결된 클라이언트는 전담(dedicated) 서버 쓰레드에 의해 동시처리적으로 서비스된다. 쓰레드는 다른 HTTP 요청을 서비스하기 전에 동기적으로 요청받은 처리를 완료한다. 그러므로, 여러 클라이언트에 대해 서비스하는동안 동기적 입출력을 실행하려면, 웹서버는 다중 쓰레드를 생성해야만 한다. 이 동기적 멀티쓰레드 모델이 직관적이고, 비교적 효과적으로 다중 CPU 체계에서 매핑된다고 할지라도, 이것은 다음과 같은 단점들을 가진다:

  • 쓰레딩 정책이 동시처리 정책과 강하게 연관되어있다. : 이 구조는 각 연결된 클라이언트들을 위한 개별적인 전담 쓰레드를 필요로 한다. 동시처리방식의 어플리케이션은 동시에 서비스되는 클라이언트의 수보다는 사용가능한 자원(쓰레드 풀링을 통한 CPU의 수와 같은 것)에 쓰레딩 전략을 맞추는 것에 의해 보다 더 최적화될 수 있다.
  •  동기화의 복잡도 증가 : 쓰레드 처리는 - 서버의 공유 자원들(캐쉬된 화일이나 웹 페이지의 히트수 기록등등)에 대한 억세스를 직렬화하기위해서 필요한 - 동기화 체계에 대한 복잡도를 증가시킨다.
  • 성능상의 과부하 증가 : 쓰레드 처리는 컨택스트 스위칭, 동기화, CPU간의 데이타 이동등에 기인하여 과부하상태로 실행될 수 있다.
  •  비호환성: 쓰레드는 모든 운영체계에 가능하지 않을 수도 있다. 게다가, 운영체계는 선점형 그리고 비선점형 쓰레드의 견지에서 보았을 경우 상당히 차이가 있다. 이런 이유로, 운영체계 플렛폼에 상관없이 동일하게 동작하는 다중 쓰레드방식의 서버를 만드는 것은 여려운 일이다.


이런 단점들 때문에, 멀티쓰레딩은 동시처리 방식의 웹서버를 개발하는데 있어서는 종종 아주 효율적이지도 않고 구조가 그리 간단하지도 않은 솔루션이 되고 있다.

 

반응적(reactive) 이벤트 디스패칭을 통한 동시처리

또다른 동기적방식의 웹서버를 구현하는 일반적인 방법은 반응적(reactive) 이벤트 디스패칭 모델을 사용하는 것이다. reactor 패턴은 어떻게 어플리케이션이 Initiation Dispatcher를 사용하여 이벤트 핸들러를 등록할 수 있는지 보여준다. Initiation Dispatcher는 블록킹 없이 명령이 입회(initiate)가능할 경우 그에 알맞는 이벤트 핸들러를 알려준다. The Initiation Dispatcher notifies the Event Handler when it is possible to initiate an operation without blocking. 싱글쓰레드 기반의 동시처리 방식 웹서버는 reactive 이벤트 디스패칭 모델을 사용할 수 있다. (이 모델은 reactor가 알맞은 명령이 들어왔음을 알려줄 때 까지 이벤트 루프상에서 기다리는 구조를 가진다.) 웹서버상의 reactive 명령에 대한 예는 Initiation Dispatcher을 사용한 acceptor의 등록작업이다. 데이타가 네트워크 연결을 통해서 도착하면, 디스패쳐는 acceptor를 콜백한다. acceptor는 네트워크 연결을 수락하고 HTTP 핸들러를 생성한다. 그런 다음 이 HTTP 핸들러는 웹서버의 싱글쓰레드 제어하에서 방금 진행되는 연결로 전송되어온 URL 요청을 처리하기 위해 reactor에 등록된다.


figure 3과 4는 반응적 이벤트 디스페칭을 사용하여 디자인된 웹서버가 여러개의 클라이언트를 어떻게 다루는지를 보여준다. figure 3는 웹서버로 클라이언트가 접속할때 밟게되는 단계를 보여주며, figure 4는 웹서버가 어떻게 클라이언트 요청을 처리하는지 보여준다.

figure3.PNG

Figure 3. 클라이언트가 Reactive 웹서버에 접속


figure 3의 단계는 다음과 같이 요약될 수 있다:

   1. 웹서버는 신규 접속을 accept처리하기 위한 Initiation Dispatcher에 acceptor를 등록한다. (The Web Server registers an Acceptor with the Initiation Dispatcher to accept new connections.)
   2. 웹서버는 Initiation Dispatcher의 이벤트루프를 동작시킨다.
   3. 클라이언트가 웹서버에 접속한다.
   4. acceptor가 신규 접속의 발생여부를 Initiation Dispatcher에게 알려주고 acceptor는 신규 접속을 받아들인다.
   5. acceptor는 신규 클라이언트에 서비스하기위해 HTTP 핸들러를 생성한다.
   6. HTTP 핸들러는 클라이언트의 요청 데이타를 읽기위해 Initiation Dispatcher에 접속정보를 등록한다. (다시말하면, 접속상태를 "읽기대기"모드로 설정한다.)
   7. HTTP 핸들러 서비스는 신규 클라이언트로부터의 요청에 따라 서비스를 시작한다.


figure4.PNG

Figure 4. 클라이언트가 HTTP Request를  Reactive 웹서버에 요청


그림 4는 reactive 웹서버가 HTTP GET 요청을 서비스하는 단계를 보여준다. 그 과정은 다음과 같다:

   1. 클라이언트는 HTTP GET 요청을 전송한다.
   2. 클라이언트의 요청 데이타가 서버에 도착하였을때 Initiation Dispatcher는 HTTP 핸들러에게 그 사실을 알려준다.
   3. 요청 데이타가 비블록상태로 읽혀진다. (이것은 읽기명령이 즉시 수행을 끝내지 못했을 경우 EWOULDBLOCK을 반환하는 상태를 말한다.) HTTP 요청데이타가 완전히 읽혀질때까지 2번과 3번단계를 반복한다.
   4. HTTP 핸들러는 HTTP 요청을 파싱한다.
   5. 요청된 화일을 동기적으로 화일 시스템으로 부터 읽는다.
   6. HTTP 핸들러는 데이타를 보내기위해 Initiation Dispatcher에 연결정보를 등록한다. (다시 말하면, 접속상태가 쓰기대기상태로 된다.)
   7. Initiation Dispatcher는 TCP 접속이 쓰기모드 상태라는 것을 HTTP 핸들러에게 알려준다.
   8. 요청 데이타가 클라이언트에게 비블록상태로 전송되어진다. (이것은 쓰기명령이 즉시 수행을 끝내지 못했을 경우 EWOULDBLOCK을 반환하는 상태를 말한다.) HTTP 요청데이타가 완전히 전송되어질때까지 7번과 8번단계를 반복한다.


reactive 이벤트 디스페칭 모델이 적용된 웹서버에 대한 C++ 코드 예제가 부록 A.2에 첨부되어있다. Initiation Dispatcher가 별도의 단일 쓰레드 상에서 실행되고 있기 까닭에 네트워크 입출력 명령들이 블록당하지 않는 상태로 reactor의 제어 아래에서 실행된다. If forward progress is stalled on the current operation, the operation is handed off to the Initiation Dispatcher, which monitors the status of the system operation. 명령이 다시 우선 처리되는 상황이 오면, 알맞은 이벤트 핸들러에게 이 사실이 알려지게 된다.


reactive 모델의 주된 장점은 이식성, coarse-grained 동시처리 제어에 따른 낮은 과부하 (다시말하면, 싱글스레드 방식은 동기화나 컨텍스트 스위칭이 요구되지 않는다.), 디스페칭 체계로 부터 어플리케이션 로직을 분리함으로서 얻을수 있는 모듈화의 잇점들을 들 수 있다. 그럼에도 불구하고, 이 방식은 다음과 같은 단점들을 가지고 있다:


프로그램 복잡도의 증가 : 앞에서 언급했듯이, 서버가 특정 클라이언트에 대해 블록당하지 않고 서비스를 실행하려면 프로그래머는 복잡한 로직을 작성해야만 한다.

  • 멀티쓰레딩에 대한 운영체계 지원의 부족 : 대부분의 운영체계들은 reactive 디스페칭 모델을 select() 함수로 구현한다[7]. 어쨌거나, select()는 같은 descriptor set에서 한개이상의 쓰레드의 사용을 허용하지 않는다. 이것은 reactive 모델이 고성능의 서버의 제작에는 맞지 않다는 의미가 된다. (하드웨어 병렬처리를 효율적으로 이용하려면 멀티쓰레드의 사용은 필수적이기 때문이다.)
  • 실행가능한 task들의 스케줄링: 선점형 쓰레드를 지원하는 동기방식의 멀티쓰레딩 구조하에서는, 설치된 CPU를 가지고 실행가능한 쓰레드들을 스케줄하고 시분할하여 제어하는 것은 운영체계의 역할이라고 할 수 있다. 이 스케줄링은 어플리케이션에서 한개의 쓰레드만이 존재하는 reactive 구조에서는 사용될 수 없다. 그러므로, 시스템 개발자는 웹서버에 연결된 모든 클라이언트들의 요청을 처리하는데 있어서 이 1개의 쓰레드의 실행단위를 주의깊게 시분할할 필요가 있다. 이것은 짧은 주기로 비블록 명령들을 실행함으로서 구현될 수 있다.


요약하면, 이런 단점들 때문에 reactive 모델은 하드웨어 병렬처리가 지원된다면 그렇게 높은 효율을 기대할 수 있는 모델이 아니다. 이 모델은 또한 서버를 코딩하는 데 있어서 흔히 요구되는 입출력의 블록킹 회피를 구현하려면 다소 높은 수준의 프로그래밍 복잡도를 극복해야만 한다.

 

해결책 : proactive 처리를 통한 동시처리

OS 플렛폼이 비동기 명령들을 지원할 경우, 고성능의 웹서버를 구현하는 효율적이고 편리한 방법은 proactive 이벤트 디스페칭을 사용하는 것이다. proactive 이벤트 디스페칭을 사용하여디자인된 웹 서버는 한개이상의 쓰레드를 제어하여 비동기명령의 완료여부를 다루는 것이 가능하다. 따라서, proactor 패턴은 완료 이벤트 디멀티플렉싱과 이벤트 핸들러 디스페칭을 통합함으로써 비동기방식의 웹서버 구조를 단순화시킨다.


비동기 웹서버는 운영체계에 처음에 비동기명령을 시동할 때와 명령이 완료했을 때를 알려주기 위한 완료 발송자(completion dispatcher)에 콜백함수를 등록하기 위해 proactor 패턴을 사용할 수 있다. 운영체계는 이때 웹서버입장에서 명령을 수행하며 순차적으로 운영체계 내의 잘 알려진 곳에 결과를 적재(queue)한다. 완료 발송자(Completion Dispatcher)는 완료 알림메세지들을 뽑아내고(dequeue), 어플리케이션 동작위주의 웹서버 코드를 담은 알맞은 콜백함수를 실행하는 역할을 담당한다.


그림 5와 6은 proactor 패턴방식의 이벤트 디스패칭을 사용하여 디자인된 웹서버가 한개 이상의 쓰레드내에서 여러 클라이언트들을 어떻게 동시처리하는지를 보여준다.
figure5.PNG

Figure 5. 클라이언트가 Proactive 방식의 웹서버에 접속


그림 5는 클라이언트가 웹서버로 접속했을때 실행되는 단계의 순서를 보여준다.

   1. 웹서버는 acceptor에게 비동기 accept 처리를 초기화하도록 알려준다.
   2. acceptor는 운영체계의 기능을 이용하여 비동기 accept 요청을 초기화하고, 그 자신을 완료 핸들러(Completion Handler)와 완료 발송자(Completion Dispatcher)의 참조로써 넘기게 된다. (이것은 비동기 accept의 완료여부를 acceptor에게 알려주는데 사용된다.)
   3. 웹서버는 완료 발송자의 이벤트 루프를 실행한다.
   4. 클라이언트가 웹서버에 접속한다.
   5. 비동기 accept 명령이 완료하면, 운영체계는 완료 발송자에게 통지한다.
   6. 완료 발송자는 acceptor에게 통지한다.
   7. acceptor는 HTTP 핸들러를 생성한다.
   8. HTTP 핸들러는 클라이언트로 부터 전송되는 요청 데이타를 비동기적으로 읽는 작업을 초기화하고 그 자신을 완료 핸들러(Completion Handler)와 완료 발송자(Completion Dispatcher)의 참조로써 넘기게 된다. (이것은 비동기 읽기작업의 완료여부를 acceptor에게 알려주는데 사용된다.)

 

figure6.PNG

Figure 6. 클라이언트가 Proactive 방식의 웹서버에 요청을 보냄


그림 6은 proactor 패턴을 적용한 웹서버가 HTTP GET 요청을 서비스하기위한 단계를 보여준다. 이 단계는 아래와 같다.


   1. 클라이언트가 HTTP GET 요청을 전송한다.
   2. 읽기 작업이 완료되면 운영체계는 완료 발송자에게 통지한다.
   3. 완료 발송자는 HTTP 핸들러에게 통지한다. (2단계와 3단계는 전체 요청 메세지가 모두 전송받아질 때까지 반복하게 된다.)
   4. HTTP 핸들러는 요청 데이타를 파싱한다.
   5. HTTP 핸들러가 동기적으로 요청된 화일을 읽어들인다.
   6. HTTP 핸들러는 화일 데이타를 접속된 클라이언트로 전송하기위한 비동기 명령을 초기화한다. 그리고 그 자신을 완료 핸들러(Completion Handler)와 완료 발송자(Completion Dispatcher)의 참조로써 넘기게 된다. 이것은 비동기 화일전송작업의 완료여부를 HTTP 핸들러에게 통지하는데 사용된다.
   7. 전송작업이 완료되면 운영체계는 완료 발송자에게 통지한다.
   8. 이때 완료 발송자는 완료 핸들러에게 통지한다. (6~8단계는 화일이 모두 전송될때까지 반복된다.)


웹서버에 proactor 이벤트 디스페칭 모델을 적용한 C++ 코드예제가 8장에 소개되어있다. proactor 패턴을 적용했을 때 가장 큰 잇점은 다중으로 실행되는 동시처리 명령들을 꼭 여러개의 쓰레드를 필요로 하지않으면서 병렬적으로 시동하고 실행할 수 있다는 점이다. 각 명령들은 비동기적으로 어플리케이션에 의해 시동되며, 운영체계의 입출력 부속시스템내에서 완료될 때 까지 실행을 계속한다. 이제 명령들을 초기화하는 쓰레드는 한가지 작업만을 전담하지 않고 추가된 요청들을 서비스해주는 것이 가능하다. 예를 들자면, 앞의 예제에서 완료 발송자는 단일 쓰레드 방식이 될 수 있는 것이다. HTTP 요청이 서버에 도착하면, 단일 완료발송자 쓰레드는 요청 메세지를 파싱하고, 화일을 읽고, 클라이언트에게 요청에 대한 응답을 전송한다. 응답이 비동기적으로 보내어지기 때문에, 다수의 응답을 동시에 부분적으로 처리할 수 있게 되는 것이다. 게다가, 동기적 화일읽기 작업은 비동기 화일읽기 작업으로 교체할 수도 있을 것이다. (이러면 동시처리될 가능성이 더 높아지게된다.) 만약 화일읽기작업이 비동기적으로 수행된다면, HTTP 핸들러에 의해 처리되는 단 하나의 동기적 작업은 HTTP 프로토콜 파싱밖에 없게 된다.


proactor 모델의 주요 단점은 reactor 모델보다 프로그래밍 로직이 보다 더 복잡해질 수 있다는 것 이다. 게다가, 비동기 명령들은 가끔 예측하기 힘들고 반복되지않는 실행순서를 가지는 까닭에 proactor 패턴은 실행 분석과 디버그하기가 다소 어렵다. 7장은 비동기 어플리케이션을 단순화시켜주는 (비동기 완료 토큰[8]과 같은) 다른 패턴들을 적용시키는 방법에 대해 설명하고 있다.

 

적용해야 할 경우

proactor 패턴은 다음과 같은 조건을 한개 이상 만족할 때 사용하기를 권장한다.

  • 호출되는 쓰레드를 블록하지 않고 한개 이상의 비동기 명령들을 실행할 필요가 있을 때.
  • 비동기 명령들이 완료될 때를 통지받아야 할 때.
  • 입출력 모델에 독립적으로 다양한 동시처리 전략이 요구될 때.
  • 어플리케이션 독립적으로 구현된 하부구조로부터 어플리케이션 의존적인 로직을 흡수할 경우 잇점이 많을 때.
  • 멀티쓰레딩 방식 혹은 reactor 디스패칭 방식으로는 성능이 기대한 것보다 낮거나 비효율적인 경우.

 

구조와 구성요소들

proactor 패턴의 구조는 figure 7에 OMT 표기법으로 그려져있다.
figure7.PNG

Figure 7. Proactor 패턴의 구성요소들


proactor 패턴의 핵심 구성요소는 다음과 같다:

  • Proactive Initiator (웹서버 어플리케이션의 주 쓰레드) : 어플리케이션 요소중의 하나이며 비동기 명령을 초기화 한다. Proactive Initiator는 완료 핸들러를 완료 발송자에게 등록한다. 완료 디스패쳐는 명령이 완료되었을때 통보해준다.
  • 완료(Completion) 핸들러 (the Acceptor and HTTP Handler): Proactor패턴에서는 완료 핸들러를 비동기 명령이 완료되었을때 어플리케이션으로 통보해주기위한 인터페이스로 사용한다.
  • 비동기 명령 (the methods Async Read, Async Write, and Async Accept) : 비동기 명령은 어플리케이션의 입장에서 요청을 실행하기 위한 용도로 사용된다. 어플리케이션이 비동기 명령을 호출하면 해당 명령은 어플리케이션의 스레드 컨트롤을 가져오지 않고 수행된다.(이에 반해 reactive 이벤트 분배 모델은 명령은 동기적으로 수행하기위해 어플리케이션의 스레드 컨트롤을 빼앗아온다) 따라서, 어플리케이션의 관점에서 보면 명령은 비동기적으로 수행된다. 비동기 명령이 완료되면 비동기 명령 프로세서는 어플리케이션에게 통보하는 일을 완료 분배자에게 위임한다.
  • 비동기 명령 프로세서 (the Operating System) : 비동기 명령 프로세서는 비동기 명령을 실행한다(명령이 완료될때까지). 이 컴포넌트는 일반적으로 OS에 의해 구현된다.
  • 완료(Completion) 분배자 (the Notification Queue) : 완료 분배자는 비동기 명령이 완료되었을때 어플리케이션의 완료 핸들러를 호출해준다. 비동기 명령 프로세서가 비동기적으로 초기화 명령을 완료했을때도 완료 분배자는 어플리케이션의 입장에서 콜백을 수행해준다.

 

Collaborations

모든 비동기 명령에 대해 몇개의 잘 디자인된 단계가 있다. 높은 수준의 추상화 레벨에서 어플리케이션은 비동기적으로 명령을 초기화하고 명령이 완료되었을때 통보받는다. Figure 8은 패턴 구성요소들 사이에 반드시 일어나야하는 다음 상호작용들을 보여준다.

figure8.PNG

Figure 8. Proactor패턴을 위한 상호작용 다이어그램


   1. Proactive 초기자는 명령을 초기화 : 비동기 명령을 수행하기 위해서 어플리케이션은 비동기 명령 프로세서에서 명령을 초기화 해야한다. 예를 들면 웹서버는 OS에게 특정한 소켓을 이용하여 파일을 네트워크 너머로 전송하겠다고 요청할수도 있다. 그런 명령을 요청하기 위해서는 웹서버는 반드시 사용하고자하는 파일과 네트워크 커넥션을 명시해야한다. 더욱이, 웹서버는 파일 전송이 완료되었을때 어떤 완료 핸들러로 통보를 받을지, 어떤 완료 분배자가 콜백을 수행해줄지도 명시해야한다.
   2. 완료 명령 프로세서는 명령을 수행 : 어플리케이션이 명령을 호출하면 비동기 명령 프로세서는 비동기적으로 명령을  수행해준다. 현대 OS(솔라리스나 윈도우 NT 같은)는 커널단에서 비동기 IO 서브시스템을 제공해준다.
   3. 비동기 명령 프로세서는 완료 분배자에게 완료를 통보 : 명령이 완료되었을 때, 비동기 명령 프로세서는 명령이 초기화될때 명시되었던 완료 핸들러와 완료 분배자를 찾아낸다. 그리고 비동기 명령의 결과와 콜백을 위한 완료 핸들러를 완료 분배자에게 넘겨준다. 예를 들어 비동기 파일 전송이 완료되었을때 명령의 결과(성공인지 실패인지)와 몇바이트가 전송되었는지를 알려준다.
   4. 완료 분배자는 어플리케이션에게 완료를 통보 : 완료 분배자는 결과 데이터를 어플리케이션에게 알려주기 위해 완료 핸들러를 호출한다. 예를 들어 비동기 읽기 명령이 완료되면 완료 핸들러는 일반적으로 새로 도착한 데이터(방금 읽어낸 데이터)의 포인터를 넘겨준다.

 

결론

이 장은 Proactor 패턴의 장단점을 설명한다.

장점

proactor 패턴은 다음과 같은 장점들을 가지고 있다:

  • 고려사항에 대한 구분이 보다 더 명확함: Proactor 패턴은 어플리케이션과는 독립적인 비동기 체계들을 어플리케이션 고유의 기능과 분리시켜준다. 어플리케이션에 독립적인 메커니즘들(비동기 명령과 관련된 완료 이벤트들을 어떻게 디멀티플렉싱하는가, 완료 핸들러에 정의된 콜백 함수를 어떻게 분배하는가)은 재사용이 가능한 컴포넌트가 된다. 마찬가지로 어플리케이션에 명시된 기능들은 특정한 서비스들을 수행한다(HTTP처리와 같은).
  • 어플리케이션 로직의 이식성 증가 : 이벤트 디멜티플렉싱을 수행하는 OS의 호출로부터 독립적인 인터페이스는 어플리케이션의 이식성을 향상시켜준다. 이런 시스템 호출들은 여러 이벤트로부터 동시에 발생할수도 있다. 이벤트 소스는 IO포트, 타이머, 동기화 객체, 시그널 등을 포함할 수도 있다. 실시간 POSIX 플랫폼에서 비동기 함수들은 aio family of API들로 부터 제공된다. 윈도우즈 NT에서는 IO completion port나 overlapped IO로 비동기 IO를 구현한다.
  • 완료 분배자가 동시처리 체계를 은폐(encapsulate)시켜준다: 완료 분배자를 비동기 명령 프로세서로부터 분리시키는 장점중의 하나는 어플리케이션이 다른 구성요소들에게 영향을 미치지 않으면서도 완료 분배자를 다양한 동시처리 전략으로 설정할수 있다는 점이다. 섹션 7에서 논의된 바와 같이 완료 분배자는 싱글스레드 혹은 스레드 풀과 같이 다양한 동시처리 전략으로 설정이 가능하다.
  • 쓰레딩 정책이 동시처리 정책과 분리된다.: 비동기 명령 프로세서가 오래 걸릴수도 있는 명령들을 Proactive Initiator를 대신하여 처리해주기 때문에 어플리케이션은 굳이 스레드를 여러개 생성하지 않아도 된다. 이것은 어플리케이션이 스레딩 정책을 독립적으로 설정할수 있게 해준다. 예를 들어 웹서버는 하나의 CPU에 하나의 스레드만을 사용할수도 있다. 하지만 많은 수의 브라우저들의 끊임없는 요청을 처리해주기 위해서는 더 많은 스레드가 필요할 수도 있다.
  • 효율의 증가: 멀티스레드 OS는 여러 스레드사이에 컨텍스트 스위칭을 반복수행한다. 이런 작업중에 OS가 놀고있는 스레드에 컨텍스트 스위칭을 한다면 전체 어플리케이션의 성능은 두드러지게 떨어질수 있다. 예를 들어 비효율적인 스레드에 완료를 poll 한다거나. Proactor 패턴은이벤트 진행의 컨트롤이 가능한 스레드만 활성화 시키기 때문에 이런 컨텍스트 스위칭 비용이 들지 않는다. 예를 들면 웹서버는 현재 pending 되어있는 GET 요청이 없다면 HTTP 핸들러를 활성화 시키지 않는다.
  • 어플리케이션 동기화의 단순화 : 완료 핸들러 자체에서 스레드를 생성하지 않는 한, 어플리케이션 로직은 동기화 이슈에 대한 고려없이 작성되어도 된다. 완료 핸들러는 마치 전통적인 싱글 스레드 환경에 있는 것 처럼 작성될수도 있다. 예를 들면 웹서버의 HTTP Get 핸들러는 비동기 읽기 명령(윈도우즈의 TransmitFile 함수와 같은)을 통해 디스크를 액세스 할 수 있다.

 

단점

proactor 패턴은 다음과 같은 단점들을 가지고 있다:

   1. 디버그하기 어렵다 : proactor 패턴을 사용하여 개발된 어플리케이션은 디버그하기가 어려워질 수 있다. (이것은 뒤집어진 제어 흐름이 프레임워크 하부구조와 어플리케이션에서 정의된 핸들러상의 콜백사이를 왔다갔다하기 때문이다. 한마디로 추적하다보면 내가 어디있지? 하는 부분을 말한다.) 이 때문에 프레임워크의 실행과정 중에 "한줄한줄씩 밟아나가는 방식"의 디버그는 정말로 어려워질 수 있다. (어플리케이션 개발자들은 프레임워크의 소스 코드를 가지고 있지 않거나 이해하지 못하기 때문이다) 이것은 LEX나 YACC으로 쓰여진 컴파일러의 구문 분석기나 파서를 디버그할 때 부딛히는 문제와 비슷하다. 이런 형태의 어플리케이션에서는, 제어되는 쓰레드가 사용자 정의된 액션 루틴상에 있을때 디버깅은 수월해질 수 있다. (In these applications, debugging is straightforward when the thread of control is within the user-defined action routines.) 한번 제어 쓰레드가 유한결정오토마타(DFA : Deterministic Finite Automata)를 생성하고 반환되면, 어찌되었던간에 프로그램 로직을 따라가는 것은 힘들어진다.


   2. 두드러진(outstanding) 명령처리와 스케줄링 : Proactive Initiators는 비동기 명령들이 실행되는 순서를 조정할 수 없을 수도 있다. 그러므로, 비동기 명령 프로세서는 비동기 명령들의 우선순위지정과 취소기능을 지원하도록 주의깊게 제작되어야 한다.

 

구현

Proactor 패턴은 다양한 방법으로 구현될 수 있다. 이 장은 Proactor 패턴을 구현하는데 필요한 단계를 알아본다.

 

비동기 명령 프로세서의 구현

Proactor 패턴을 구현하는 첫번째 단계는 비동기 명령 프로세서는 만드는 일이다. 비동기 명령 프로세서는 어플리케이션을 대신하여 명령을 비동기적으로 수행해주는 역할을 한다. 이것의 결과로 비동기 명령 API들을 정의하는 일과 비동기 명령 엔진을 만드는 두 가지 해야 할 일이 생긴다.

 

비동기 명령 API를 정의하기

비동기 명령 프로세서는 반드시 비동기 명령을 요청할 수 있는 API들을 제공해야 한다. 아래에 API들을 디자인하는데 고려해야 할 사항이 있다.

  • 이식성: API는 어플리케이션이나 Proactor Initiator에 종속되어서는 안된다.
  • 융통성: 종종 비동기 API는 많은 타입의 명령에 공유된다. 예를 들면 비동기 IO 명령은 다중 IO작업을 수행하기 위한 방편으로 사용될수도 있다(네트워크나 파일 같은). 이런 재사용을 지원하도록 API를 디자인하는 것이 유익할 수 있다.
  • 콜백 : Proactor Initiator는 반드시 명령이 호출될때 콜백을 등록해야한다. 콜백을 구현하기위한 일반적인 방법은 calling object나 caller라고 불리는 인터페이스를 export하는 방법이 있다. Proactor Initiator는 반드시 비동기 명령 프로세서에게 명령이 완료되면 호출될 완료 핸들러를 알려야한다.
  • 완료 디스패쳐 : 어플리케이션이 다중 완료 분배자를 사용할 수도 있기 때문에Proactor Initiator 또한 어떤 완료 분배자가 콜백을 수행해 줄 것인지 명시해야 한다.

     

    이런 사항들을 감안한, 비동기 읽기와 쓰기를 제공하는 API가 있다.  Asynch_Stream클래스는 비동기 읽기와 쓰기를 초기화 하는 팩토리이다. 한번 생성되면 여러개의 읽기와 쓰기 작업이 이 클래스를 이용하여 시작될 수 있다.  비동기 읽기 작업이 완료되면Asynch_Stream::Read_Result 클래스가 완료 핸들러에 있는 handle_read 함수를 통해 전달될 것이다. 마찬가지로 비동기 쓰기 작업이 완료되면Asynch_Stream::Write_Result 클래스가 완료 핸들러에 있는 handle_write 함수를 통해 전달될 것이다.

비동기 명령 엔진 구현하기

비동기 명령 프로세서는 반드시 명령을 비동기 방식으로 수행하는 메커니즘을 포함해야한다. 다르게 설명하면, 어플리케이션 스레드가 비동기 명령을 수행하면 이 명령은 어플리케이션의 스레드 컨트롤을 가져오지 않고 수행되어야 한다(블로킹되지 않고 바로 리턴되어야 한다는 의미). 다행히도 현대의 OS들은 비동기 명령을 위한 메커니즘을 제공한다(예를 들면 POSIX 비동기 I/O나 Windows NT의 overlapped I/O). 이 경우에는 이 패턴을 구현하는 부분은 단순히 플랫폼의 API들과 위에 서술된 비동기 명령들과 매핑만 시켜주면 된다. 하지만 만일 OS가 비동기 명령을 지원하지 않는다면 비동기 명령엔진을 구현하는 몇가지 테크닉이 있다. 가장 직관적인 해결법은 전용 스레드에서 어플리케이션의 요청한 비동기 명령을 수행하는 방법일 것이다. 스레드된 비동기 명령을 구현하기 위해서는 몇가지 단계가 있다.

 

알려진 사용처

아래는 Proactor 패턴을 사용했다고 알려진 곳들이다.

  • I/O Completion  Ports in Windows NT : Windows NT는 Proactor 패턴을 구현하고 있다. 새로운 네트워크 커넥션을 받는 명령, 파일이나 소켓을 읽거나 쓰는 명령, 네트워크 커넥션을 통해 파일을 전송하는 명령이 Windows NT에 의해 제공된다. OS가 바로 비동기 명령 프로세서 이다. 명령의 결과는 I/O completion port에 쌓인다(이것이 완료 분배자의 역할을 수행).
  • The Unix AIO Family of Asynchronous I/O Operations : 실시간 POSIX 플랫폼에서는 Proactor패턴이 aio family of APIS로 구현되어있다. 이런 OS의 기능들은 위에 기술된 Windows NT와 상당히 유사하다. 한가지 다른 점이 있다면 UNIX 시그널은 진짜 비동기 완료 분배자로 구현되어있다.(Windows NT의 API는 진짜 비동기 방식은 아니다)
  • ACE Proactor : ACE의 비동기 컴포넌트들은 Windows NT의 I/O Completion Ports와 UNIX 플랫폼의 aio APIs를 캡슐화한다. ACE Proactor의 추상화는 Windows NT의 표준 C APIs를 통해 객체지향 인터페이스를 제공한다. 소스코드는 ACE 웹사이트에서 얻을수 있다(www.cs.wustl.edu/_schmidt/ACE.html).
  • Asynchronous Procedure Calls in Windows NT : 어떤 시스템(Windows NT와 같은)은 비동기 프로시져 콜(APC)을 지원한다. APC는 함수는 비동기적으로 특정한 스레드에서 실행되게 해준다. APC가 스레드에 queue되면 시스템은 소프트웨어 인터럽트를 실행한다. 다음번에 스레드가 schedule되면 APC에서 실행된다. OS에 의해 만들어지는 것을 커널모드 APC라고 하고 어플리케이션에 의해 만들어지는 것을 유저모드 APC라고 한다.

 

관련이 있는 패턴들

Figure 9는 Proactor와 관련이 있는 패턴들을 보여주고 있다.
figure9.PNG

Figure 9. Proactor패턴과 연관된 패턴들


The Asynchronous Completion Token (ACT) 패턴은 일반적으로 Proactor 패턴과 결함되어 쓰인다. 비동기 명령이 완료되었을때, 어플리케이션은 이벤트를 적절하게 다루기 위해 단순히 통보만 받는 것보다 더 많은 정보가 필요할 수도 있다. The Asynchronous Completion Token 패턴은 어플리케이션이 비동기 동작 완료와 관련있는 정보를 효율적으로 알수 있게 해준다.

출처 : http://serverprogramminggem.springnote.com/pages/3912089

posted by Sunny's
2010. 8. 24. 17:59 Pattern
출처 : http://cloudree.egloos.com/4454163

본문 인용 :

프로그래밍 기법 중의 하나인, 디자인 패턴을 배우고 싶으신 분에게 추천하는 책입니다.
책 구성은 어찌보면 정신 없을 수도 있지만, 
그만큼 설명이 잘 되어있기 때문에 천천히 잘 따라가다보면 이해가 쉽습니다.

Head First Design Patterns (한빛 미디어, 서환수 역)

책에 있는 코드를 Java 대신 C#으로 제가 직접(!) 작성한 화일도 첨부합니다.
사실 C# 은 Java와 매우 비슷해서 거의 1:1 변환이 가능했습니다만,
몇가지 Java에 의존적인 예제와, 너무 소스가 큰것은 포기할 수 밖에 없었네요.

아래 내역은, 책 내용을 스터디 일환으로 정리했던 자료입니다.

PS. 그런데 이걸 IT 밸리로 보내도 되는 걸까나... 프로그래밍 밸리를 만들어 달라는 건 무리겠죠?



=====================================================

Head First Design Patterns 정리 by cloudree.egloos.com
경고) 책을 안 읽고 아래 내역을 보면 절대 이해 안 됨.

1.개요
1.잘 짜여진 구조를 편하게 적용
2.용어를 통한 쉽고 빠른 소통
3.OOP 가 문법이라면, DP는 구성방법
4.여러 DP를 조합해서 사용하는 경우도 많음
5.만능은 아니다

2.Strategy Pattern
1.(오리,인형오리,로켓오리)가 우는 방법, 나는 방법
2.상속되는 개체별로 많이 바뀌는 것을 (상속으로 구현하지 않고) 멤버로 구현
3.isA (상속) => hasA (구성)
1.상속의 단점 : 서브클래스에서 코드가 중복된다. 실행시 특징을 바꾸기 힘들다

3.Observer Pattern
1.날씨 확인 장치
2.주제와 옵저버
1.필요시 옵저버로 등록/제거
2.주제는 데이터를 옵저버에게 전송 (일대다)
3.주제이면서 옵저버일수 있음
3.느슨한 결합 Loose Coupling
4.Push / Pull

4.Decorator Pattern 
1.수많은 종류의 커피의 가격 계산
2.객체에 추가적인 요건을 동적으로 첨가.
3.인터페이스는 바꾸지 않고 책임(기능)만 추가
4.Cost = costWhip( costMocha( costRoast( =0.99 ) + 0.2 ) + 0.1 )
5.OCP : Open-Close-Principle

5.Factory Pattern
1.피자 가게
2.여러 종류의 객체를 생성하는 방법
3.Simple Factory (Pattern에는 부족)
4.의존성 뒤집기 원칙 : Dependency Inversion Principle : 추상화된 것에 의존하도록 만들기. 구상 클래스에는 의존하지 않게.
1.일반적 사고 : 구상(피자 가게) => 다양한 피자(구상) => 피자(추상)
2.뒤집기 사고 : 피자(추상) => Factory => 다양한 피자(구상) 
5.가급적 신경 써줄 가이드 라인
1.구상클래스에 대한 레퍼런스 사용 회피
1.CheesePizza *pizza 대신 Pizza *pizza 사용
2.구상 클래스에서 유도된 클래스 정의 회피
1.class NYStyleCheesePizza : public CheesePizza
3.베이스 클래스에서 구현된 메소드를 오버라이드 회피
1.베이스 클래스 자체가 잘 만들어진 추상 클래스가 아니라는 뜻
6.Factory Method Pattern
1.서로 연관된 또는 의존적인 객체들로 이루어진 제품군을 생성하기 위한 인터페이스를 저공. 
2.생성할 구상 클래스를 서브클래스에서 결정함
7.Abstract Factory Pattern
1.객체를 생성하기 위한 인터페이스를 만듬. 
2.클라이언트에서 구상클래스를 지정하지 않으면서도 일군의 객체를 생성할 수 있도록 함
8.FMP vs AFP 비교
1.여러 가지 상속을 통해 각기 다른 종류의 객체 생성(FMP) / (AFP) 구성(Composition)을 통해 다른 종류의 객체 생성
2.연관된 일련의 객체 생성에 유리(FMP) / (AFP)어떤 구상 클래스가 사용될지 미리 알수 없는 경우

6.Singleton Pattern
1.클래스 인스턴스가 하나만 만들어지도록 하고, 그 인스턴스에 대한 전역 접근을 허용
2.멀티프로세싱 주의

7.Command Pattern
1.식당에서 주문 → 웨이터 → 주방장
2.특정 객체에 대한 특정 작업 요청을 캡슐화

8.Adaptor Pattern
1.오리와 칠면조
2.한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환. 어댑터를 이용하면 인터페이스의 호환성 문제 때문에 같이 쓸 수 없는 클래스들를 연결해서 쓸 수 있음.
3.인터페이스를 변환
4.Object Adaptor : 구성(Composition)을 사용
5.Class Adaptor : 다중 상속으로 양쪽에 대해 adapt

9.Facade (퍼사드)
1.홈시어터 = DVD, 앰프, 프로젝터, 조명, 스크린 …
2.어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공. 퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용
3.단순화된 인터페이스로 감쌈. 원래 것도 사용가능
4.최소 지식 원칙 : 상호작용 클래스 갯수를 줄이기 = 데메테르의 법칙
1.객체 자체
2.메소드에 매개변수로 전달된 객체
3.그 메소드에서 생성하거나 인스턴스를 만든 객체
4.그 객체에 속하는 구성요소

10.Template Methods Pattern
1.커피/차 끓이기의 공통점
2.메소드에서 알고리즘의 골격을 정의. 알고리즘의 여러 단계 중 일부는 서브클래스에서 구현 가능. 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의 가능
3.Hook : 확장을 위해 빈 메소드를 끼워넣어 두는 것 : 추상 함수로 만들지 않아도 되니 상속시의 부담이 줄어듬
4.헐리우드 원칙
1.먼저 연락하지마. 내가 먼저 연락할테니까
2.의존성 부패 (dependency rot) 방지 : 저수준 구성요소는 고수준 구성요소를 호출할 수 없게 구성

11.Iterator Pattern
1.식당메뉴+팬케이크메뉴
2.컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 하는 방법을 제공
3.응집도(Cohesion) : 클래스를 바꾸는 이유는 한가지 뿐 이어야 함 = 한 클래스에서는 한가지 역할만
4.Null Iterator : Null Object Design Pattern
12.Composite Pattern
1.식당(디저트)+팬케이크+디너
2.객체들을 크리구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들수 있음.
3.서브메뉴의 구현:복합 객체와 개별 객체를 같은 식으로 관리

13.State Pattern
1.GumBallMachine
2.객체의 내부 상태가 바뀜에 따라 객체의 행동(beahavior)을 바꿔서 마치 객체의 클래스가 바뀌는 것 같은 결과
3.구성으로 가지고 있는 상태 변수를 다른 동작으로 상속된 구상 클래스의 인스턴스로 교체
4.스테이트 패턴 : 상태를 기반으로 하는 행동을 캡슐화 하고 행동을 현재 상태에 위임
  스트래티지 패턴 : 알고리즘의 각 단계를 구현하는 방법을 (전략적으로) 서브클래스에서 구현
  템플릿 메소드 패턴 : 바꿔쓸수 있는 행동(method)을 (템플릿에) 캡슐화한 다음 실제 행동은 다른 객체에 위임

14.Proxy Pattern
1.로컬용으로 만든 GumballMachine 모니터에 (원격 프록시)를 달아서 원격 처리
2.어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
3.데코레이터 : 클래스에 새로운 행동을 추가하기 위한 용도
  프록시 : 클래스에 대한 접근을 제어하기 위한 용도
  어댑터 : 다른 객체의 인터페이스를 바꿔주는 용도
  퍼사드 : 여러 객체를 감싸서 인터페이스를 단순화 시킵니다.
4.동적 프록시, 가상 프록시, 원격 프록시, 보호 프록시
  

15.Compound Pattern
1.여러 패턴을 섞어쓰는 패턴 : 꽥카운터(데코레이터)+꽥옵저버+오리팩토리+오리컴포지트 of [오리(스트래티지)+아답터(거위)]...
- 어댑터로 거위를 감싸서 오리처럼 처리
- 꽥 횟수를 세기위해 데코레이터 패턴 도입
- 오리 종류 추가를 편하게 하기 위해 팩토리 패턴 도입
- 오리떼 관리를 위해 컴포지트 패턴 도입
- 오리 소리를 추적하기 위해 옵저버 패턴 도입
2.모델-뷰-콘트롤러 (MVC) 
- 스트래티지 패턴 : 뷰에서 "유저 행동"에 관련된 작업을 컨트롤러에게 위임
- 옵저버 패턴 : 모델의 변경을 뷰/콘트롤러가 관찰
- 컴포지트 패턴 : 뷰의 UI는 컴포지트 체계

16.기타 패턴
1.Bridge Pattern : 구현과 인터페이스의 분리
2.Builder Pattern : 복합객체 생성 과정을 캡슐화
3.Chain of Responsibility : 역할 사슬 = 윈도우 이벤트 드라이버
4.Flyweight : 가벼운 나무 그리기 객체
5.Interpreter : 인터프리터
6.Mediator : 통신 중앙 집중형
7.Memento : 상태와 객체의 분리 = 객체 상태의 저장 및 복원
8.Prototype : 인스턴스의 복사를 통한 객체 생성
9.Visitor : traverser 객체가 Composite 객체의 기능을 보완
17.맺음
- [특정 컨텍스트 내에서 주어진 문제에 대한 해결책]
- 반복적으로 적용 가능한 상황에서 제약조건에 맞게 사용
- 어떤 컨텍스트 내에서 일련의 제약조건에 의해 영향을 받을수있는 문제에 본착했다면
그 제약조건 내에서 목적을 달성하기 위한 해결책을 찾아낼수 있는 디자인을 적용하면 된다
- GoF 의 패턴 카탈로그
posted by Sunny's
2009. 3. 11. 13:15 Pattern

제목 : 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와 실버라이트에서는 데이터 바인딩이 동기화를 적용하기 위한 자연스런 선택이 되었습니다. 

YouCard Screenshot
   
제가 이 패턴의 예제로 앞으로 사용할 애플리케이션은 저의 오스트리아 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)과 리팩토링 후의 원하는 디자인을 보여줍니다. 

youcardviewmodeldiagram

첫번째 작업은 외부 서비스를 위한 인터페이스를 정의하는 것입니다. 저는 ITwitter나 IFlickr 인터페이스를 정의하는 대신 IMicroBlog 나 IPhotoService같은 보다 일반적인 이름을 사용하기로 결정했습니다. 만약 우리가 FriendFeed나 Picasa, Windows Live Photo Gallery 같은 서비스를 지원한다고 할 때 이 편이 보다 합리적이라고 생각합니다. 그리고 현재의 코드를 각각의 인터페이스의 구체적인 구현(implementation)으로 리팩토링 할 것입니다.  하나는 실제 온라인 서비스를 위한 것이고 하나는 더미 데이터(역자주: 실제 서비스로 부터 불러오는 데이터가 아닌 직접 코딩, 하드 코딩을 통한 데이터)를 이용한 가짜 구현이 될 것입니다.  앞서 말했듯이 이 애플리케이션은 리믹스의 Creative 트랙을 위해 만들어진 데모이고 블랜드에서의 좋은 디자인 경험을 위해 만들어졌습니다. 때문에 디자이너가 실제 코드가 어떻게 작동하는지 알 수 있게 하기 위해 더미 데이터를 생성해야만 했습니다. 만약 XAML을 사용한 디자이너-개발자 워크플로우의 이점을 활용하려고 한다면, 디자인 툴에서 코드가 어떻게 작동할지 생각해 봐야 합니다. 현재 구현 에서는, 만약 생성자에서 블랜드에서 실행되는 것인지 아닌지를 체크하고 있다면  if-문을 통해서 YouCardData 클래스 안에 더미 데이터를 제공 할 수 있을 것입니다. 만약 블랜드 안에서 실행되고 있다면 YouCardData 클래스는 Twitter와 Flickr 서비스의 가짜 구현부분을 이용할 것이고 브라우저에서 실행되고 있다면 YouCardData안에 timer가 실행되고 실제 구현부분을 통해 Twitter와 Flickr를 통해 실제 데이터를 다운 받기 시작할 것입니다. 이것과 관련된 YouCardData 생성자의 중요한 부분은 다음과 같습니다. 

 Code sample 1

이 코드에는 Dependency Injection(역자주: http://jonas.follesoe.no/YouCardRevisitedImplementingDependencyInjectionInSilverlight.aspx 참고)에 대한 선언이 들어가 있는데 이 것에 대한 내용은 다음 포스트에서 다루도록 하겠습니다. 여기서 현재 애플리케이션 Object 의 Type을 체크합니다. 실제로 실행중인 애플리케이션이 본래 애플리케이션의 객체를 반환할 때 블랜드는 블랜드 자신의 애플리케이션 오브젝트를 반환하게 됩니다. 

 리팩토링이 필요한 애플리케이션의 다음 조각은 메인 사용자 인터페이스(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으로 사용하게 됩니다. 

Code sample 2

 이제 두 UI가 View-Models와 바인딩 되는 경우를 통해 데이터를 보여주는 법을 다룰 수 있게 되었습니다. 그러나 이제 반 정도 왔을 뿐입니다. 우리는 사용자가 Card를 추가하고 삭제하는 것을 View-Models와 어떻게 상호작용(Interact) 할 것인지 알아야 합니다. 

YouCard interaction photo

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를 실행시키기 위해 다음과 같은 코드를 사용하고 있습니다. 

Code sample 3

그는 부착 프로퍼티(Attached Proeprty) 를 사용하여  Button 에 Search TextBox의 TextProperty를 파라미터로 넘겨주고 View-Model의 Search 메소드를 불러주는 작동을 추가해 주었습니다.  다음 포스팅에서 Nikhil은 Dynamic Language 런타임을 사용하여 어떻게 좀더 간결한 코드를 얻을 수 있는지 보여주고 있습니다. (get the syntax even more compact using the Dynamic Language Runtime)

Code sample 4
이 접근법의 근사한 점은 연결된 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 코드는 다음과 같습니다. 

Code sample 5

텍스트 박스는 View-Model에서 Username 프로퍼티와 바인딩되어 있습니다. 두 컨트롤 모두 View-Model의 Username을 파라미터로 받아 AddCard Command를 부릅니다. 

커멘드들은 다음과 같이 정의 됩니다. 

Code sample 6

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 클래스에 카드를 추가하거나 삭제하는 것을 조정해주고자 합니다:

Code sample 7
또한 AddUser 버튼의 활성, 비활성(enabling/disabling) 부분도 언급하는게 좋을 것 같습니다. 이것은 버튼의 IsEnabled 프로퍼티를 View-Model의 IsAddEnabled 프로퍼티와 데이터 바인딩 해줌으로써 구현할 수 있습니다. IsAddEnabled 프로퍼티는 다음과 같습니다. 

mvvp-code8

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.!! 
posted by Sunny's
prev 1 next