허당 레몬도리
 
어느덧 2009년도 다 가고 2010년이 다가오고 있다. 항상 이맘때가 되면 느끼는 것이지만 시간은 결코 필자를 기다려 주지 않는다. 올해도 특별히 한 일이 없어 보이지만 어느새 1년이란 시간이 가 버렸다고 생각하니 허무하기 그지없다. 새해에는 Visual Studio 2010도 출시된다고 하고 닷넷 프레임워크 4.0 버전도 나올 것이지만 준비해 놓은 것이 없어서 마음만 조급하고 연초에 세웠던 목표는 올해도 이루기 어려울 것으로 보인다. 그렇다고 가는 해를 붙잡고 아쉬워만 할 수는 없는 일. 가는 2009년을 빨리 정리하고 다가오는 새해를 준비해야 할 것 같다. 올해의 마지막 칼럼인 이번 칼럼에서는 지난 10월호에서 다루었던 WCF의 신뢰할 수 있는 서비스 구축 방법과 11월호에서 다루었던 MSMQ(Microsoft Message Queue)를 접목하는 방법에 대해 살펴보고자 한다.

유경상  http://www.simpleisbest.net | 필자는 현재 드원 테크놀러지사에 근무하고 있다. COM+, 닷넷 등 주로 마이크로소프트의 기술에 대한 교육과 컨설팅을 맡고 있다. 머리가 백발이 돼서도 기술서적을 집필하는 게 꿈이다.

 

MSMQ 기반 서비스 개요
10월호에서는 네트워크 통신 상에서 발생될 수 있는 메시지 유실과 같은 신뢰성 문제를 WCF에서 어떻게 해결하고 있는가를 살펴보았다. WCF는 웹 서비스의 표준인 WS-ReliableMessaging(WS-RM) 스펙을 통해 웹 서비스 호출의 신뢰도를 유지할 수 있으며, WCF의 프로그래밍 추상 계층은 개발자가 WS-RM에 대해 상세한 내용을 모fms다 하더라도 WCF의 표준적인 프로그래밍 모델(Address, Binding, Contract)을 통해 신뢰할 수 있는 통신 메커니즘을 구현하고 있음을 알 수 있었을 것이다.

WCF의 WS-RM 기반의 신뢰할 수 있는 세션(Reliable Session)도 극복할 수 없는 장애는 존재한다. 예를 들어 <그림 1>과 같이 서비스 혹은 서비스를 호스팅 하는 서버가 어떤 오류에 의해 다운되었다고 가정해 보자. 새로이 구동된 클라이언트나 이미 신뢰할 수 있는 세션을 맺어 놓은 클라이언트는 서비스로부터 수신 확인을 알리는 ACK(Acknowledge) 메시지를 수신하지 못하기 때문에 일정 시간 동안 재전송을 반복할 것이다. 하지만 이 재전송이 모두 실패하면 서비스에 접속하지 못하거나 맺어 놓은 신뢰할 수 있는 세션은 종료될 것이다. 또 서비스가 정상적으로 구동하고 있다 할지라도 클라이언트와 서비스 사이의 네트워크에 수십 분 혹은 몇 시간 동안 장애가 발생하면 신뢰할 수 있는 세션을 사용한다 할지라도 SOAP 메시지는 정상적으로 전달되지 않을 것이다.

이러한 식의 장애는 WCF가 제공하는 WS-RM 기반의 신뢰할 수 있는 세션이 극복하는 장애와는 근본적으로 같지 않다. 신뢰할 수 있는 세션은 네트워크에 대한 일시적인 장애와 같이 클라이언트와 서비스 사이의 네트워크 상 장애를 극복하고자 하는 것이지만 서비스 자체가 구동되어 있지 않거나 아예 클라이언트 혹은 서비스가 네트워크에 접속되어 있지 않은 상태에서 서비스 메시지를 전달하는 오프라인 상황과 같은 장애를 극복하는 것은 아니다.

이러한 오프라인 상황이나 장시간의 네트워크 장애까지도 극복할 수 있는 것이 11월호에서 살펴보았던 메시지 큐이다. 다행스럽게도 WCF는 MSMQ를 트랜스포트로 사용하는 바인딩을 제공한다. 표준 바인딩으로는 NetMsmq Binding과 MsmqIntegrationBinding이 제공되며 커스텀 바인딩을 위해 MsmqTransportBindingElement 클래스가 제공된다. MSMQ 기반의 바인딩들은 서비스 호출을 MSMQ 메시지로 포장하여 메시지 큐를 통해 전송하도록 구성되어 있다. 만약 서비스를 호스팅하는 서버가 다운 상태라면 MSMQ 메시지는 클라이언트 측의 보내는 큐(outgoing queue)에서 대기하게 되며 지속적으로 재시도를 수행한다. 나중에 서버가 다시 구동되면 클라이언트 측에 쌓여있던 MSMQ 메시지는 결국 서버로 전달될 것이다. 비슷하게 클라이언트와 서버 사이의 네트워크에 장애가 발생하더라도 클라이언트 측 메시지 큐에 쌓여있던 서비스 호출 메시지는 네트워크 장애가 제거되면 서비스에 전달되어 최종적으로 서비스 호출은 성공하게 된다. <그림 2>는 서비스 호출 시 MSMQ를 사용할 때 서비스 호출 메시지가 클라이언트로부터 서비스까지 어떻게 전달되는지 보여준다.

MSMQ 기반 WCF 서비스 구축
이제 구체적으로 WCF 상에서 MSMQ를 이용하여 어떻게 서비스를 호출하는지 살펴보도록 하겠다. MSMQ에 대해 생소한 독자가 있다면 지난 11월호의 필자의 칼럼을 먼저 읽어 보기 바란다. 또한 이 칼럼은 독자들이 WCF 프로그래밍 모델에 대해 기본적인 지식이 있다고 가정할 것이다. WCF에 대해 생소한 독자들이 있다면 관련 자료나 도서를 먼저 참고하길 바라는 바이다.

MSMQ를 트랜스포트로 사용하는 WCF 클라이언트와 서비스는 서로 직접적으로 연결되지 않으며 클라이언트는 클라이언트 측의 보내는 큐(outgoing queue)와 상호 작용하고 서비스는 서버 측의 수신 큐와 상호작용하게 된다. 클라이언트가 서비스를 호출하면 서비스 호출에 대한 SOAP 메시지는 MSMQ 메시지로 포장되어 MSMQ 인프라를 통해 서비스 측의 수신 큐에 전송된다. WCF는 MSMQ 메시지 전송에 어떤 관여도 하지 않으며 메시지 전송을 100% MSMQ 인프라에 의지하게 한다는 말이다. MSMQ 기반의 WCF 서비스의 채널 리스너(listener)는 다른 트랜스포트와 달리 네트워크를 리스닝하지 않는다. 대신 수신 큐에 메시지가 수신되었는가를 감시하며 수신 큐에 MSMQ 메시지가 도착하면 이 메시지에서 SOAP 메시지를 추출하여 서비스를 호출하는 데 사용한다. 이 때 큐에 존재하는 처리해야 할 메시지의 개수에 따라서 여러 개의 쓰레드가 생성되어 동시에 메시지들이 처리될 수도 있다. 최종적으로 클라이언트가 전송한 SOAP 메시지가 서비스에 도달하게 되어 WCF 서비스의 메쏘드가 호출되게 된다. <그림 3>은 이와 같은 상황을 잘 보여주고 있다.

WCF의 MSMQ 지원 기능은 복잡한 MSMQ의 상세 사항을 개발자에게 감추어 준다. WCF 클라이언트가 일반 서비스를 호출하듯이 서비스 메쏘드를 호출하면 이 호출은 SOAP 메시지로 직렬화되며 직렬화된 SOAP 메시지는 MSMQ 트랜스포트 채널에 의해 MSMQ 메시지로 포장된다. 실제로 개발자는 MSMQ 메시지가 관여되는지도 감지하지 못한다. 서비스 역시 일반적인 TCP, HTTP 트랜스포트가 사용될 때와 마찬가지로 서비스 메쏘드가 호출되는 것으로만 느껴진다. 하부에 MSMQ 메시지가 전송되고 수신 큐에 메시지가 쌓이는 등의 MSMQ 세부 사항으로부터 서비스는 분리될 수 있는 것이다.

MSQM 기반의 서비스와 클라이언트가 <그림 3>과 같은 작동 방식을 가지기 때문에 높은 신뢰성을 유지할 수 있다. 예를 들어 서비스가 다운되어 있는 경우를 생각해 보자. 서비스가 다운되어 있더라도 클라이언트의 서비스 호출은 MSQM 메시지로 포장된 채로 수신 큐에 쌓여 있을 것이며 나중에 서비스가 다시 구동되면 수신 큐에 쌓여있던 서비스 호출은 처리되기 시작할 것이다. 또 다른 예로써 서비스가 구동되어 있더라도 네트워크 장애가 발생하였거나 서비스 측 컴퓨터가 다운되어 있다면, 서비스에 대한 호출은 클라이언트 측의 전송 큐에 쌓여 있을 것이다. 그리고 MSMQ 인프라는 지속적인 메시지 재전송을 시도할 것이며 나중에 네트워크의 장애가 사라지고 서비스 측 컴퓨터가 다시 수행되면 그 때 서비스 호출 메시지가 수신 큐까지 배달될 것이며 최종적으로 서비스 호출은 성공하게 될 것이다. 이처럼 MSMQ를 사용하는 메시지 큐 기반의 WCF 서비스는 클라이언트에게 매우 높은 신뢰도를 제공하는 서비스 환경을 제공할 수 있는 것이다.

메시지 큐 기반의 서비스가 사용하는 계약은 MSMQ의 특성을 그대로 물려받게 된다. 지난 11월호 칼럼에서 MSMQ 기반의 통신은 근본적으로 비동기 성향을 가지며 단방향 호출 성향을 갖는다고 하였다. 마찬가지로 메시지 큐 기반의 WCF 서비스는 비동기적인 성격을 갖고 단방향 호출 성향을 갖는다. 따라서 NetMsmqBinding을 사용하는 서비스의 계약은 반드시 OperationContract 특성의 IsOneWay 속성 값이 true이어야만 한다. 서비스 호스트가 Open될 때 WCF는 MSMQ 트랜스포트 기반의 바인딩이 서비스 종점에 사용되면 서비스 계약의 모든 메쏘드에 IsOneWay 속성의 값이 true 인가를 확인한다. 만약 이 제약을 만족하지 않는 계약이 MSMQ 트랜스포트 기반의 바인딩과 함께 사용되면 “계약이 요청-응답 형식이지만 바인딩이 이를 지원하지 않는다”는 내용의 InvalidOperation Exception이 발생한다. IsOneWay 속성의 값이 true이면 서비스 계약 내의 메쏘드들은 모두 반환 타입이 void이어야 함도 잊지 말자.

[ServiceContract]
interface IQueuedContract
{
    [OperationContract(IsOneWay = true)]
    void AsyncOneWayMethod(string arg0, int arg1);
}

클라이언트가 MSMQ 트랜스포트를 사용하는 서비스를 호출하면 클라이언트 측의 프록시와 채널은 호출을 SOAP 메시지로 직렬화하고 이 메시지를 MSMQ 메시지로 포장하여 보내는 큐에 집어넣는 작업만을 수행한다. 그리고 이 작업이 성공하면 클라이언트에게 마치 서비스 호출이 성공한 것처럼 제어를 반환한다. 아쉽게도 서비스에 대한 호출이 성공했는지 클라이언트가 알아낼 방법은 없다. 다시 말해 클라이언트 측에서 프록시가 서비스 호출에 대해 예외를 발생하지 않았다는 말은 서비스 호출에 대한 메시지를 MSMQ 내에 성공적으로 삽입했다는 말 이다. 클라이언트는 서비스에 대한 호출 메시지가 서비스 측에 도착했는지, 서비스 메쏘드가 성공적으로 호출되었는지 클라이언트는 즉시 알 수 없으며 서비스에서 발생한 예외 또한 클라이언트에게 알려지지 않는다.

이런 관점에서 보면 메시지 큐 기반의 서비스에 대한 호출은 비동기적인 성격을 갖는다고 할 수 있다. 클라이언트는 서비스를 호출할 때 전송 큐에 메시지를 삽입하는 짧은 시간 동안만 블록될 뿐이다. 그리고 서비스가 호출을 처리하는 동안 다른 작업을 수행할 수 있기 때문에 비동기적인 성격을 갖는다고 말할 수 있다. 물론 메시지 큐 기반의 서비스를 WCF가 기본적으로 제공하는 비동기 호출 메커니즘을 통해 호출할 수도 있다. 하지만 이 비동기 호출은 클라이언트 측에서 전송 큐에 메시지를 삽입하는 작업을 비동기적으로 수행한다는 말일 뿐이다.

WCF는 서비스 계약에 “이 계약이 메시지 큐를 사용해야 함”을 명시할 수도 있다. WCF에서 제공하는 Delivery Requirements 특성의 QueuedDeliveryRequirements 속성을 이용하여 서비스 계약(혹은 서비스 계약을 구현하는 서비스 타입)에 대해 MSMQ를 통해 바인딩을 구성할 것을 강제할 수 있는 것이다.

[AttributeUsage(AttributeTargets.Class | AttributeTargets. Interface,
                   AllowMultiple = true)]
public sealed class DeliveryRequirementsAttribute : Attribute, ......
{
    public QueuedDeliveryRequirementsMode QueuedDeliveryRequirements
    { get; set; }
    ......
}

public enum QueuedDeliveryRequirementsMode
{
    Allowed = 0,
    Required = 1,
    NotAllowed = 2
}

QueuedDeliveryRequirements 속성은 단순히 true/false를 나타내지 않고 큐를 사용한 메시지 전송 모드를 나타낸다. Allowed 값은 디폴트 값으로써 이 서비스 계약을 메시지 큐 기반 바인딩에 사용할 수도 있고 그렇지 않을 수도 있음을 나타내며 Required 값은 이 계약을 반드시 큐를 사용하는 NetMsmqBinding이나 MSMQ 트랜스포트를 사용하는 바인딩에 사용할 것을 나타낸다. 반면 NotAllowed 값이 명시되면 이 계약을 MSMQ 기반으로 하는 바인딩과 함께 사용하면 InvalidOperationExcep tion이 발생한다. 어떤 서비스 계약이 MSMQ와 함께 사용될 것으로 설계되었다면 명시적으로 DeliveryRe quirements 특성을 인터페이스에 표시하여 다른 바인딩과 사용되지 못하도록 하는 것이 좋다.

[ServiceContract]
[DeliveryRequirements(
              QueuedDeliveryRequirements = QueuedDeliveryRequirementsMode.Required)]
interface IQueuedContract
{
    [OperationContract(IsOneWay = false)]
    void AsyncOneWayMethod(string arg0, int arg1);
}

메시지 큐 기반 서비스의 주소
서비스가 MSMQ 기반의 바인딩을 사용할 때 서비스 종점(service endpoint)의 주소는 net.msmq로 시작하는 스킴(scheme)을 사용한다. TCP 트랜스포트가 net.tcp를 명명된 파이프 트랜스포트가 net.pipe 인 점을 감안하면 일관적인 주소 명명법이라 말할 수 있다. net.msmq 스킴 뒤에는 컴퓨터 이름이 포함되어야 하며 컴퓨터 이름 뒤에는 private 혹은 public으로 시작하는 URI가 붙어야 한다. private는 서비스에서 사용하는 큐가 개인 큐임을 나타내며 public은 서비스가 공용 큐를 사용함을 나타낸다. 이 때 public은 생략이 가능하다.

개인 큐/공용 큐를 구별하는 식별자 다음에는 MSMQ에 생성한 큐의 이름이 나타나면 된다. 다음은 메시지 큐 기반의 서비스에서 유효한 서비스 종점 주소를 보여준다.

net.msmq://localhost/private/wcftest
net.msmq://simpleisbest.net/private/wcf/msmq/dataservice
net.msmq://theserver/public/bookservice
net.msmq://theserver/bookservice
net.msmq://192.168.1.220/private$/customqueue

예제로 보여준 서비스 종점 주소들 중에서 첫 번째와 두 번째의 주소는 각각 로컬 컴퓨터와 simpleisbest.net이란 컴퓨터에 존재하는 개인 큐를 사용하는 서비스의 주소이다. 서비스 종점 주소에서 private 혹은 public 뒤에 붙는 큐의 이름은 일반 URI처럼 사용될 수 있다. 하지만 큐의 이름이 정확하게 이 URI와 일치해야 함에 주의하자. 즉 위의 예에서 첫 번째 예에서 사용하는 개인 큐 이름은 wcftest이며 두 번째 예제에서 사용하는 개인 큐의 이름은 wcf/msmq/dataservice이다. 이들 큐가 존재하지 않는다면 서비스는 ServiceHost.Open 호출은 실패하게 된다. 공용 큐를 사용하는 서비스의 종점 주소는 private 대신 public을 사용하며 public은 생략이 가능하다. 위의 주소 예제에서 세 번째와 네 번째 주소는 같은 서비스의 주소이며 공용 큐의 이름은 bookservice이다.

HTTP나 TCP와 마찬가지로 MSMQ 트랜스포트의 주소도 서버의 이름대신 IP를 직접 사용할 수 있다. 직접 IP를 하드 코딩하는 것보다 컴퓨터의 이름을 쓰는 것이 언제나 더 좋은 방법이지만 IP를 이용하여 직접적으로 메시지 큐 기반 서비스의 주소를 나타낼 수 있다는 것만 알아두자.

메시지 큐 기반 서비스/클라이언트의 작동 방식 테스트

구체적으로 큐 기반 서비스를 작성하여 테스트 해 봄으로써 MSMQ가 WCF 서비스에 어떻게 작용하는지 살펴보도록 하자. <리스트 1>은 간단한 메시지 큐 기반 서비스의 예제를 보여주고 있다. 필수 사항은 아니지만 앞서 설명한 대로 서비스 계약에 DeliveryRequirements 특성을 명시하고 QueuedDeliveryRequirements 속성의 값을 Required로 설정함으로써 MSMQ를 반드시 사용하도록 강제하고 있음에 주목하자. 또 MSMQ 트랜스포트를 사용함에 따라 단방향 호출 방식을 사용하도록 IsOneWay 속성을 true로 설정하고 메쏘드의 반환 타입 역시 void 타입으로 설정하였다.

<리스트 1> 메시지 큐 기반 예제 서비스 코드

[ServiceContract]
[DeliveryRequirements(
              QueuedDeliveryRequirements = QueuedDeliveryRequirementsMode.Required)]
interface IQueuedContract
{
    [OperationContract(IsOneWay = true)]
    void AsyncOneWayMethod(string arg0, DateTime arg1);
}

class QueuedService : IQueuedContract
{
    public void AsyncOneWayMethod(string arg0, DateTime arg1)
    {
        Console.WriteLine("Service method invoked... arg0={0}, arg1={1}",
                             arg0, arg1);
    }
}

이 서비스를 호스팅하는 코드는 <리스트 2>와 같다.

<리스트 2> 메시지 큐 기반 서비스를 호스팅 하는 예제 코드

static void Main(string[] args)
{
    Console.WriteLine("Simple MSMQ-based WCF Service Sample......");

    if (MessageQueue.Exists(".\\private$\\wcftest") == false) {
        MessageQueue.Create(".\\private$\\wcftest", true);
    }

    NetMsmqBinding binding = new NetMsmqBinding();
    binding.Security.Mode = NetMsmqSecurityMode.None;

    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
    behavior.HttpGetEnabled = true;
   
    ServiceHost host = new ServiceHost (typeof(QueuedService),
        new Uri("net.msmq://localhost/private/ wcftest"),
        new Uri("http://localhost/wcf/example/ msmq/basic"));
    host.AddServiceEndpoint(typeof(IQueuedContract), binding, "");
    host.Description.Behaviors.Add(behavior);

    host.Open();
    Console.WriteLine("The service started. Press any key to stop...");
    Console.ReadKey(true);
    host.Close();
}

<리스트 2>의 호스팅 코드는 먼저 서비스의 종점이 사용할 큐가 존재하는지 확인한다. System.Messaging 네임스페이스의 MessageQueue 클래스의 Exists 메쏘드는 공용 큐와 로컬 컴퓨터의 개인 큐의 존재 여부를 반환해 준다. 만약 큐가 존재하지 않는다면 Create 메쏘드를 호출하여 큐를 생성한다. 이와 같은 과정을 반드시 코드를 통해 수행할 필요는 없다. MSMQ 관리도구 스냅 인을 사용하여 큐를 생성하는 것도 가능하다. 어떤 방법이건 큐의 이름을 명시할 때는 서비스의 종점이 사용하는 서비스 주소와 큐의 이름이 일치해야 해야 한다.

<리스트 2>에서 주의해야 할 사항은 NetMsmqBinding의 보안 설정이다. Active Directory 도메인 환경이 아닌 상황에서 개인 큐를 사용할 때는 WCF의 보안은 크게 제약을 받는다.

X.509 인증서 기반의 메시지 보안만을 사용할 수 있으며 이외의 메시지 보안 및 트랜스포트 보안은 사용할 수 없다. 이외에 NetMsmqBinding을 사용할 때 주의할 사항은 서비스의 메타데이터를 공개하는 방법이다. HTTP 트랜스포트나 TCP 트랜스포트, 명명된 파이프 트랜스포트는 HTTP GET 방식이나 WS-Metadata Exchnage 표준을 사용하는 MEX(Metadata Exchange) 종점을 통해 메타데이터를 제공할 수 있다. 하지만 MSMQ는 단방향 통신만을 지원하고 단방향 통신은 서비스 결과를 수신할 없기 때문에 MEX 종점을 사용할 없다. 따라서 <리스트 2>에서는 ServiceHost 객체에 MSMQ 베이스 주소 외에 HTTP 베이스 주소를 추가로 설정하고 ServiceMetadata Behavior 객체의 HttpGetEnabled 속성을 true로 지정하여 Visual Studio나 svcutil.exe를 통해 클라이언트 측 프록시 코드를 자동으로 생성할 수 있도록 지정하였다.

<리스트 2>와 같이 코드를 통해 호스트 코드를 생성할 수도 있으며 <리스트 3>과 같이 configuration을 사용할 수도 있다.

<리스트 3> Configuration을 통한 메시지 큐 기반 서비스 구성

<system.serviceModel>
  <bindings>
    <netMsmqBinding>
      <binding name="MsmqBindingConfig">
        <security mode="None" />
      </binding>
    </netMsmqBinding>
  </bindings>
  <services>
    <service name="BasicMsmqServiceApp.QueuedService"
               behaviorConfiguration="MetaConfig">
      <host>
        <baseAddresses>
          <add baseAddress="net.msmq:// localhost/private/wcftest"/>
          <add baseAddress="http://localhost/wcf/ example/msmq/basic"/>
        </baseAddresses>
      </host>
      <endpoint address=""
            binding="netMsmqBinding"
            bindingConfiguration="MsmqBindingConfig"
            contract="BasicMsmqServiceApp. IQueuedContract" />
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="MetaConfig">
        <serviceMetadata httpGetEnabled="true"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

IIS 7.0에서 메시지 큐 기반 서비스를 호스팅 할 때는 <리스트 3>과 같이 configuration을 사용하는 것이 훨씬 더 좋지만 간단한 테스트를 위해서는 <리스트 2>와 같은 코드를 사용하는 것이 더 편리하다.

이제 클라이언트 코드를 살펴보자. Visual Studio의 서비스 참조나 svcutil.exe를 이용하여 프록시 코드를 자동으로 생성하면 <리스트 4>와 같은 클라이언트 코드를 작성할 수 있다. 이 클라이언트 코드는 MSMQ를 사용하지 않는 다른 서비스를 호출하는 것과 전혀 다를 바 없는 평범한 코드이다.

<리스트 4> 메시지 큐 기반 서비스를 호출하는 클라이언트 코드 예제

static void Main(string[] args)
{
    Console.WriteLine("Basic MSMQ-based WCF Client Sample....");
    using (QueuedContractClient proxy = new QueuedContractClient()) {
        proxy.AsyncOneWayMethod("str value", DateTime.Now);
    }
}

이제부터 재미있는 테스트를 수행해 보자. 서비스를 구동하지 않은 상태에서 <리스트 4>를 수행해 보자. 만약 MSMQ가 아닌 다른 트랜스포트를 사용하면 ServerToo BusyException 예외가 발생했을 것이다. 만약 신뢰할 수 있는 세션을 사용하면 SendTimeout동안 이 예외가 발생하지 않겠지만 시간이 지나면 결국엔 ServerTooBusyEx ception이 발생한다. 그러나 NetMsmqBinding을 사용한다면 클라이언트는 아무런 일도 없다는 듯이 서비스 호출을 성공적으로 완료한다. 아무런 지연 시간도 없이 말이다.

다시 한번 반복하지만 클라이언트가 서비스 호출에 성공했다는 말은 서비스 호출을 담는 SOAP 메시지를 MSMQ 메시지로 포장하여 보내는 큐에 삽입했다는 말과 같다. 이 예제에서는 수신 큐가 로컬 컴퓨터에 존재하기 때문에 보내기 큐 보다는 수신 큐로 곧바로 MSMQ 메시지를 전송했다고 하는 편이 맞을 것이다. 실제로 메시지가 수신 큐에 존재하는지 MSMQ 관리도구 스냅 인을 활용하여 확인해 보자. MSMQ 관리 스냅 인에서 wcftest 개인 큐를 찾아 확인해 보면 <화면 1>과 같이 하나의 메시지가 수신되어 있는 것을 알 수 있을 것이다. 또 수신 된 메시지의 속성을 확인해 보면 <화면 2>와 같이 메시지 본문을 확인할 수 있는데, 메시지의 본문은 바이너리 인코딩 된 SOAP 메시지가 포함되어 있다. NetMsmqBinding 이 바이너리 인코딩을 사용하기 때문이다.

만약 서비스가 클라이언트와 같은 컴퓨터가 아닌 원격 컴퓨터에 존재했다면, 다시 말해 클라이언트가 사용하는 서비스 종점 주소가 net.msmq://localhost/private/ wcftest가 아닌 net.msmq://Win2008testbed/private/ wcftest이었다면 서비스 호출 메시지는 Win2008testbed 라는 이름을 가진 컴퓨터의 wcftest 개인 큐에 삽입되어 있을 것이다. 더욱이 이 Win2008testbed라는 컴퓨터가 오프라인 상태라면 서비스 호출 메시지는 수신 큐까지 배달되지 못한 채로 로컬 컴퓨터의 보내는 큐 안에 존재할 수도 있다.'

이제, 서비스를 구동해 보자. 서비스가 구동되면 서비스 호스트가 Open되자마자 수신 큐인 wcftest 큐에서 메시지를 읽어 서비스 메쏘드가 호출되는 것을 확인할 수 있을 것이다. 다음은 서비스의 수행 결과를 보여준다.

Simple MSMQ-based WCF Service Sample......
Service method invoked... arg0=str value, arg1=2008-08-22 오전 1:17:43
The service started. Press any key to stop...

<리스트 2>를 살펴보면 ServiceHost.Open 호출 이후에 Console.WriteLine 메쏘드를 호출하지만 Open 메쏘드가 호출되어 MSMQ 채널 리스너가 시작되자마자 큐에서 메시지를 읽어 서비스 호출을 처리하기 시작하기 때문에 WriteLine 메쏘드 호출 전에 서비스 호출이 발생했음을 알 수 있을 것이다.

한 가지 재미있는 부분은 서비스가 구동되는 시점에서 클라이언트는 이미 호출을 완료하고 종료되었다는 사실이다. 클라이언트가 수행 중이 아닌 상태에서도 서비스가 클라이언트의 호출을 수신하고 서비스 메쏘드가 호출되는 상황인 것이다. MSMQ 기반의 서비스와 클라이언트는 서비스 호출에 있어서 완전히 비동기적으로 작동할 수 있음을 잘 보여주는 상황이라 말할 수 있겠다.

마지막 테스트로 서비스가 구동된 상태에서 클라이언트를 다시 수행해 보자. 클라이언트 수행과 동시에 서비스는 메쏘드의 호출을 알리는 메시지를 출력할 것이다. 이처럼 MSMQ를 사용한다 할지라도 클라이언트와 서비스가 네트워크로 연결되어 있는 상황이라면 마치 HTTP 나 TCP 트랜스포트를 사용하는 것처럼 곧바로 서비스 호출이 이루어 진다. 물론 이런 상황이라면 메시지 큐에 메시지가 쌓인 채로 남아 있는 상황은 발생하지 않는다. 클라이언트가 서비스를 호출하자 마자 곧바로 MSMQ 메시지는 수신 큐로 전달되고 수신 큐의 서비스 호출 메시지는 MSMQ 채널 리스너에 의해 디스패치(dispatch) 되어 서비스 호출이 발생하기 때문이다.

MsmqIntegrationBinding
NetMsmqBinding은 MSMQ 메시지 내에 SOAP 메시지를 담아 서비스 호출을 수행하기 위해 제공되는 표준 바인딩이다. 반면 MsmqIntegrationBinding은 레거시(legacy) MSMQ 클라이언트와 서비스를 위해 제공되는 바인딩이다. 즉 WCF를 통해 과거의 MSMQ 애플리케이션들을 통합하고자 할 때 사용하는 바인딩이 Msmq IntegrationBinding 이라는 것이다. 이 바인딩은 MSMQ 메시지의 본문에 SOAP 메시지 대신 애플리케이션이 정의하는 데이터들을 사용할 수 있다. 이런 이유로 Msmq IntegrationBinding 을 사용하는 서비스의 계약은 몇 가지 제약을 갖는데, 서비스 메쏘드는 반드시 Msmq Message<T> 타입의 매개변수 하나만을 가져야 한다. MsmqMessage<T> 타입은 System.Messaging 네임스페이스의 Message 타입과 매우 비슷하게 MSMQ 메시지의 메시지 본문, 라벨 등의 속성을 갖는다.

예를 들어 다음과 같은 레거시 MSMQ 클라이언트 코드가 있을 때,

string queuePath = “FORMATNAME:DIRECT=OS:.\\ private$\\generictxqueue”;
MessageQueue queue = new MessageQueue(queue Path);

Message msg = new Message();
msg.Label = “OnMessage”;
msg.Body = “Hello MSMQ World !”;
queue.Send(msg, MessageQueueTransactionType.Single);

MsmqIntegrationBinding을 이용하여 메시지를 수신하는 서비스는 <리스트 5>와 같이 작성이 가능하다. 클라이언트가 전송하는 MSMQ 메시지의 본문이 string 타입이기 때문에 MsmqMessage<string> 타입을 매개변수로 사용하는 메쏘드가 서비스 계약 내에 정의되었고 string 타입의 역직렬화를 위해 ServiceKnownType 특성을 서비스 계약에 표시하였음에 주목하자. 서비스 메쏘드 구현에서는 MsmqMessage<T> 타입의 Body 속성에서 MSMQ 메시지의 본문을 읽어서 필요한 처리를 수행함에도 주목할 필요가 있다.

<리스트 5> MsmqIntegrationBinding을 사용하는 서비스 예제

[ServiceContract]
[ServiceKnownType(typeof(string))]
interface IMsmqService
{
    [OperationContract(IsOneWay = true, Action="*")]
    void OnMessage(MsmqMessage<string> msg);
}

class MsmqService : IMsmqService
{
    public void  OnMessage(MsmqMessage<string> msg)
    {
        string data = (string)msg.Body;
        Console.WriteLine("Message body = {0}", data);
    }
}

MsmqIntegrationBinding은 레거시 MSMQ 애플리케이션에 대해 메시지 수신뿐만 아니라 메시지 전송에도 사용할 수 있다. MsmqIntegrationBinding은 SOA(Service Oriented Architecture) 기반의 메시징 애플리케이션을 위한 바인딩이라기보다는 MSMQ 기반의 클라이언트/서버에 대한 또 다른 API라고 생각하는 것이 편하다. 기존의 API들이 엄연히 존재함에도 불구하고 WCF 기반의 API를 사용하는 이유를 들자면, 단일 프로그래밍 모델을 통해 다양한 통신 방식을 사용할 수 있도록 하는 WCF의 설계 목적을 들 수 있겠다. WCF의 프로그래밍 방식을 그대로 유지하면서 과거 레거시 MSMQ 애플리케이션과 통합할 수 있다는 점은 개발자에게 매력적이라 할 수 있다. WCF의 메쏘드 호출 방식 프로그래밍 모델은 메시지를 전송(send) 방식의 전통적인 MSMQ의 프로그래밍 모델보다 개발자에게 더 친숙하다.

지금까지 WCF 상에서 MSMQ가 어떻게 이용되는지 예제를 통해 간략히 살펴보았다. 앞서 살펴본 대로 WCF는 NetMsmqBinding을 통해 메시지 전달의 신뢰성을 크게 올려준다. 이 칼럼에서 소개한 것들 외에도 WCF는 MSMQ를 통해 메시지의 중복 전달을 막거나 메시지 배달 실패 시 메시지 사본을 저장해 주는 저널링(journaling), 트랜잭션 기능 지원 등 다양한 기능을 제공한다. 지면 관계상 이들 내용을 여기서 다 다룰 수 없음을 독자들은 이해해 주기 바란다. 이들에 대한 내용은 WCF 관련 서적이나 MSDN을 참고 하도록 하자.

출처 : http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=34081

profile

허당 레몬도리

@LemonDory

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!