세션의 대기 중인 메시지 그룹화
WCF(Windows Communication Foundation)에서는 단일 수신 애플리케이션에서 처리하도록 관련 메시지 집합을 그룹화할 수 있는 세션을 제공합니다. 세션의 일부인 메시지는 동일한 트랜잭션의 일부여야 합니다. 모든 메시지가 동일한 트랜잭션의 일부이므로, 하나의 메시지가 처리되지 않으면 전체 세션이 롤백됩니다. 세션은 배달 못 한 편지 큐 및 포이즌 큐와 관련하여 유사하게 동작합니다. 세션에 대해 구성된 대기 중인 바인딩에 대해 설정된 TTL(Time to Live) 속성은 세션에 전체적으로 적용됩니다. TTL이 만료되기 전에 세션에 있는 메시지 중 일부만 전송된 경우 전체 세션이 배달 못 한 편지 큐에 배치됩니다. 마찬가지로 세션에 있는 메시지가 애플리케이션 큐에서 애플리케이션으로 전송되지 못하면 전체 세션이 포이즌 큐(사용 가능한 경우)에 배치됩니다.
메시지 그룹화 예제
메시지 그룹화가 유용한 한 가지 예는 주문 처리 애플리케이션을 WCF 서비스로 구현하는 경우입니다. 예를 들어 클라이언트가 많은 항목을 포함하는 주문을 이 애플리케이션에 제출합니다. 각 항목에 대해 클라이언트가 서비스를 호출하고, 그 결과 메시지가 개별적으로 전송됩니다. 서버 A가 첫 번째 항목을 수신하고 서버 B가 두 번째 항목을 수신할 수 있습니다. 항목이 추가될 때마다 해당 항목을 처리하는 서버는 해당 주문을 찾아서 항목을 추가해야 하므로 매우 비효율적입니다. 서버에서 현재 처리 중인 모든 주문을 추적하여 새 항목이 속하는 주문을 확인해야 하기 때문에 단일 서버에서 모든 요청을 처리하는 이런 비효율성이 여전히 발생합니다. 단일 주문에 대한 모든 요청을 그룹화하면 이러한 애플리케이션의 구현이 크게 간소화됩니다. 클라이언트 애플리케이션이 세션에 있는 단일 주문에 대한 모든 항목을 보내기 때문에 서비스에서 주문을 처리할 때 전체 세션을 한 번에 처리합니다. \
절차
세션을 사용하도록 서비스 계약을 설정하려면
세션이 필요한 서비스 계약을 정의합니다. 이 작업은 다음을 지정하여 ServiceContractAttribute 특성을 통해 수행합니다.
SessionMode=SessionMode.Required
이러한 메서드는 아무것도 반환하지 않기 때문에 계약의 작업을 단방향으로 표시합니다. 이 작업은 다음을 지정하여 OperationContractAttribute 특성을 통해 수행합니다.
[OperationContract(IsOneWay = true)]
서비스 계약을 구현하고 InstanceContextMode의 InstanceContextMode.PerSession를 지정합니다. 그러면 각 세션에 대해 서비스가 한 번씩만 인스턴스화됩니다.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
서비스 작업마다 하나의 트랜잭션이 필요합니다. OperationBehaviorAttribute 특성을 사용하여 이를 지정합니다. 트랜잭션을 완료하는 작업에서는 TransactionAutoComplete를
true
로 설정해야 합니다.[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
시스템 제공
NetMsmqBinding
바인딩을 사용하는 엔드포인트를 구성합니다.System.Messaging을 사용하여 트랜잭션 큐를 만듭니다. 메시지 큐(MSMQ) 또는 MMC를 사용하여 큐를 만들 수도 있습니다. 그러면 트랜잭션 큐가 만들어집니다.
ServiceHost를 사용하여 서비스에 대한 서비스 호스트를 만듭니다.
서비스를 사용할 수 있도록 서비스 호스트를 엽니다.
서비스 호스트를 닫습니다.
클라이언트를 설정하려면
트랜잭션 큐에 쓸 트랜잭션 범위를 만듭니다.
ServiceModel Metadata 유틸리티 도구(Svcutil.exe) 도구를 사용하여 WCF 클라이언트를 만듭니다.
주문을 합니다.
WCF 클라이언트를 닫습니다.
예제
설명
다음 예제에서는 IProcessOrder
서비스와 이 서비스를 사용하는 클라이언트에 대한 코드를 제공합니다. 또한 WCF에서 대기 중인 세션을 사용하여 그룹화 동작을 제공하는 방법을 보여 줍니다.
서비스 코드
// Service Code:
using System;
using System.ServiceModel.Channels;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;
using System.Text;
using System.Collections.Generic;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required)]
public interface IOrderTaker
{
[OperationContract(IsOneWay = true)]
void OpenPurchaseOrder(string customerId);
[OperationContract(IsOneWay = true)]
void AddProductLineItem(string productId, int quantity);
[OperationContract(IsOneWay = true)]
void EndPurchaseOrder();
}
// Define the Purchase Order Line Item
public class PurchaseOrderLineItem
{
static Random r = new Random(137);
string ProductId;
float UnitCost;
int Quantity;
public PurchaseOrderLineItem(string productId, int quantity)
{
this.ProductId = productId;
this.Quantity = quantity;
this.UnitCost = r.Next(10000);
}
public override string ToString()
{
String displayString = "Order LineItem: " + Quantity + " of " + ProductId + " @unit price: $" + UnitCost + "\n";
return displayString;
}
public float TotalCost
{
get { return UnitCost * Quantity; }
}
}
// Define Purchase Order
public class PurchaseOrder
{
string PONumber;
string CustomerId;
LinkedList<PurchaseOrderLineItem> orderLineItems = new LinkedList<PurchaseOrderLineItem>();
public PurchaseOrder(string customerId)
{
this.CustomerId = customerId;
this.PONumber = Guid.NewGuid().ToString();
}
public void AddProductLineItem(string productId, int quantity)
{
orderLineItems.AddLast(new PurchaseOrderLineItem(productId, quantity));
}
public float TotalCost
{
get
{
float totalCost = 0;
foreach (PurchaseOrderLineItem lineItem in orderLineItems)
totalCost += lineItem.TotalCost;
return totalCost;
}
}
public string Status
{
get
{
return "Pending";
}
}
public override string ToString()
{
StringBuilder strbuf = new StringBuilder("Purchase Order: " + PONumber + "\n");
strbuf.Append("\tCustomer: " + CustomerId + "\n");
strbuf.Append("\tOrderDetails\n");
foreach (PurchaseOrderLineItem lineItem in orderLineItems)
{
strbuf.Append("\t\t" + lineItem.ToString());
}
strbuf.Append("\tTotal cost of this order: $" + TotalCost + "\n");
strbuf.Append("\tOrder status: " + Status + "\n");
return strbuf.ToString();
}
}
// Service class which implements the service contract.
// Added code to write output to the console window
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class OrderTakerService : IOrderTaker
{
PurchaseOrder po;
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void OpenPurchaseOrder(string customerId)
{
Console.WriteLine("Creating purchase order");
po = new PurchaseOrder(customerId);
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void AddProductLineItem(string productId, int quantity)
{
po.AddProductLineItem(productId, quantity);
Console.WriteLine("Product " + productId + " quantity " + quantity + " added to purchase order");
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void EndPurchaseOrder()
{
Console.WriteLine("Purchase Order Completed");
Console.WriteLine();
Console.WriteLine(po.ToString());
}
// Host the service within this EXE console application.
public static void Main()
{
// Get MSMQ queue name from app settings in configuration
string queueName = ConfigurationManager.AppSettings["queueName"];
// Create the transacted MSMQ queue if necessary.
if (!MessageQueue.Exists(queueName))
MessageQueue.Create(queueName, true);
// Get the base address that is used to listen for WS-MetaDataExchange requests
string baseAddress = ConfigurationManager.AppSettings["baseAddress"];
// Create a ServiceHost for the OrderTakerService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(OrderTakerService), new Uri(baseAddress)))
{
// Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
serviceHost.Close();
}
}
}
}
' Service Code:
Imports System.ServiceModel.Channels
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions
Imports System.Text
Imports System.Collections.Generic
Namespace Microsoft.ServiceModel.Samples
' Define a service contract.
<ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples", SessionMode:=SessionMode.Required)> _
Public Interface IOrderTaker
<OperationContract(IsOneWay:=True)> _
Sub OpenPurchaseOrder(ByVal customerId As String)
<OperationContract(IsOneWay:=True)> _
Sub AddProductLineItem(ByVal productId As String, ByVal quantity As Integer)
<OperationContract(IsOneWay:=True)> _
Sub EndPurchaseOrder()
End Interface
' Define the Purchase Order Line Item
Public Class PurchaseOrderLineItem
Private Shared r As New Random(137)
Private ProductId As String
Private UnitCost As Single
Private Quantity As Integer
Public Sub New(ByVal productId As String, ByVal quantity As Integer)
Me.ProductId = productId
Me.Quantity = quantity
Me.UnitCost = r.Next(10000)
End Sub
Public Overrides Function ToString() As String
Dim displayString As String = "Order LineItem: " & Quantity & " of " & ProductId & " @unit price: $" & UnitCost + Constants.vbLf
Return displayString
End Function
Public ReadOnly Property TotalCost() As Single
Get
Return UnitCost * Quantity
End Get
End Property
End Class
' Define Purchase Order
Public Class PurchaseOrder
Private PONumber As String
Private CustomerId As String
Private orderLineItems As New LinkedList(Of PurchaseOrderLineItem)()
Public Sub New(ByVal customerId As String)
Me.CustomerId = customerId
Me.PONumber = Guid.NewGuid().ToString()
End Sub
Public Sub AddProductLineItem(ByVal productId As String, ByVal quantity As Integer)
orderLineItems.AddLast(New PurchaseOrderLineItem(productId, quantity))
End Sub
Public ReadOnly Property TotalCost() As Single
Get
Dim totalCost_Renamed As Single = 0
For Each lineItem In orderLineItems
totalCost_Renamed += lineItem.TotalCost
Next lineItem
Return totalCost_Renamed
End Get
End Property
Public ReadOnly Property Status() As String
Get
Return "Pending"
End Get
End Property
Public Overrides Function ToString() As String
Dim strbuf As New StringBuilder("Purchase Order: " & PONumber & Constants.vbLf)
strbuf.Append(Constants.vbTab & "Customer: " & CustomerId & Constants.vbLf)
strbuf.Append(Constants.vbTab & "OrderDetails" & Constants.vbLf)
For Each lineItem In orderLineItems
strbuf.Append(Constants.vbTab + Constants.vbTab + lineItem.ToString())
Next lineItem
strbuf.Append(Constants.vbTab & "Total cost of this order: $" & TotalCost + Constants.vbLf)
strbuf.Append(Constants.vbTab & "Order status: " & Status + Constants.vbLf)
Return strbuf.ToString()
End Function
End Class
' Service class which implements the service contract.
' Added code to write output to the console window
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)> _
Public Class OrderTakerService
Implements IOrderTaker
Private po As PurchaseOrder
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=False)> _
Public Sub OpenPurchaseOrder(ByVal customerId As String) Implements IOrderTaker.OpenPurchaseOrder
Console.WriteLine("Creating purchase order")
po = New PurchaseOrder(customerId)
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=False)> _
Public Sub AddProductLineItem(ByVal productId As String, ByVal quantity As Integer) Implements IOrderTaker.AddProductLineItem
po.AddProductLineItem(productId, quantity)
Console.WriteLine("Product " & productId & " quantity " & quantity & " added to purchase order")
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
Public Sub EndPurchaseOrder() Implements IOrderTaker.EndPurchaseOrder
Console.WriteLine("Purchase Order Completed")
Console.WriteLine()
Console.WriteLine(po.ToString())
End Sub
' Host the service within this EXE console application.
Public Shared Sub Main()
' Get MSMQ queue name from app settings in configuration
Dim queueName As String = ConfigurationManager.AppSettings("queueName")
' Create the transacted MSMQ queue if necessary.
If (Not MessageQueue.Exists(queueName)) Then
MessageQueue.Create(queueName, True)
End If
' Get the base address that is used to listen for WS-MetaDataExchange requests
Dim baseAddress As String = ConfigurationManager.AppSettings("baseAddress")
' Create a ServiceHost for the OrderTakerService type.
Using serviceHost As New ServiceHost(GetType(OrderTakerService), New Uri(baseAddress))
' Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open()
' The service can now be accessed.
Console.WriteLine("The service is ready.")
Console.WriteLine("Press <ENTER> to terminate service.")
Console.WriteLine()
Console.ReadLine()
' Close the ServiceHostBase to shutdown the service.
serviceHost.Close()
End Using
End Sub
End Class
End Namespace
클라이언트 코드
using System;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;
namespace Microsoft.ServiceModel.Samples
{
//The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.
//Client implementation code.
class Client
{
static void Main()
{
//Create a transaction scope.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
// Create a proxy with given client endpoint configuration
OrderTakerClient client = new OrderTakerClient("OrderTakerEndpoint");
try
{
// Open a purchase order
client.OpenPurchaseOrder("somecustomer.com");
Console.WriteLine("Purchase Order created");
// Add product line items
Console.WriteLine("Adding 10 quantities of blue widget");
client.AddProductLineItem("Blue Widget", 10);
Console.WriteLine("Adding 23 quantities of red widget");
client.AddProductLineItem("Red Widget", 23);
// Close the purchase order
Console.WriteLine("Closing the purchase order");
client.EndPurchaseOrder();
client.Close();
}
catch (CommunicationException ex)
{
client.Abort();
}
// Complete the transaction.
scope.Complete();
}
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions
Namespace Microsoft.ServiceModel.Samples
'The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.
'Client implementation code.
Friend Class Client
Shared Sub Main()
'Create a transaction scope.
Using scope As New TransactionScope(TransactionScopeOption.Required)
' Create a proxy with given client endpoint configuration
Dim client As New OrderTakerClient("OrderTakerEndpoint")
Try
' Open a purchase order
client.OpenPurchaseOrder("somecustomer.com")
Console.WriteLine("Purchase Order created")
' Add product line items
Console.WriteLine("Adding 10 quantities of blue widget")
client.AddProductLineItem("Blue Widget", 10)
Console.WriteLine("Adding 23 quantities of red widget")
client.AddProductLineItem("Red Widget", 23)
' Close the purchase order
Console.WriteLine("Closing the purchase order")
client.EndPurchaseOrder()
client.Close()
Catch ex As CommunicationException
client.Abort()
End Try
' Complete the transaction.
scope.Complete()
End Using
Console.WriteLine()
Console.WriteLine("Press <ENTER> to terminate client.")
Console.ReadLine()
End Sub
End Class
End Namespace