방법: 트랜잭션 서비스 만들기
이 샘플에서는 트랜잭션 서비스 만들기의 다양한 측면과 함께, 클라이언트에서 시작한 트랜잭션을 사용하여 서비스 작업을 조정하는 방법을 보여 줍니다.
트랜잭션 서비스 만들기
서비스 계약을 만들고 TransactionFlowOption 열거형에서 원하는 설정으로 작업에 주석을 추가하여 들어오는 트랜잭션의 요구 사항을 지정합니다. 구현 중인 서비스 클래스에도 TransactionFlowAttribute를 배치할 수 있습니다. 이렇게 하면 트랜잭션 설정을 모든 구현에서 사용하는 대신 단일 구현에서만 사용하게 됩니다.
[ServiceContract] public interface ICalculator { [OperationContract] // Use this to require an incoming transaction [TransactionFlow(TransactionFlowOption.Mandatory)] double Add(double n1, double n2); [OperationContract] // Use this to permit an incoming transaction [TransactionFlow(TransactionFlowOption.Allowed)] double Subtract(double n1, double n2); }
구현 클래스를 만들고 ServiceBehaviorAttribute를 사용하여 필요한 경우 TransactionIsolationLevel 및 TransactionTimeout을 지정할 수 있습니다. 대부분의 경우 TransactionTimeout의 기본값은 60초, TransactionIsolationLevel의 기본값은 Unspecified가 적절합니다. 각 작업에 대해 OperationBehaviorAttribute 특성을 사용하여, 메서드 내에서 수행된 작업이 TransactionScopeRequired 특성의 값에 따라 트랜잭션 범위 내에서 수행되어야 하는지 여부를 지정할 수 있습니다. 이 경우
Add
메서드에 사용된 트랜잭션은 클라이언트로부터 들어오는 필수 트랜잭션과 동일하며,Subtract
메서드에 사용된 트랜잭션은 트랜잭션이 클라이언트로부터 들어오는 경우 해당 트랜잭션과 동일하며, 그렇지 않은 경우 암시적으로 로컬에서 만들어진 새 트랜잭션과 동일합니다.[ServiceBehavior( TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable, TransactionTimeout = "00:00:45")] public class CalculatorService : ICalculator { [OperationBehavior(TransactionScopeRequired = true)] public double Add(double n1, double n2) { // Perform transactional operation RecordToLog(String.Format("Adding {0} to {1}", n1, n2)); return n1 + n2; } [OperationBehavior(TransactionScopeRequired = true)] public double Subtract(double n1, double n2) { // Perform transactional operation RecordToLog(String.Format("Subtracting {0} from {1}", n2, n1)); return n1 - n2; } private static void RecordToLog(string recordText) { // Database operations omitted for brevity // This is where the transaction provides specific benefit // - changes to the database will be committed only when // the transaction completes. } }
트랜잭션 컨텍스트를 이동하도록 지정하고 이 작업에 사용할 프로토콜을 지정하여 구성 파일에서 바인딩을 구성합니다. 자세한 내용은 ServiceModel 트랜잭션 구성을 참조하십시오. 특히 바인딩 형식은 끝점 요소의 binding 특성에서 지정합니다. <endpoint> 요소에는 다음 샘플 구성에서처럼 이름이
transactionalOleTransactionsTcpBinding
인 바인딩 구성을 참조하는 bindingConfiguration 특성이 포함됩니다.<service name="CalculatorService"> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
트랜잭션 흐름은 transactionFlow 특성을 사용하여 구성 수준에서 사용하도록 설정하며, 트랜잭션 프로토콜은 다음 구성에서처럼 transactionProtocol 특성을 사용하여 지정합니다.
<bindings> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
여러 트랜잭션 프로토콜 지원
최적의 성능을 얻으려면 WCF(Windows Communication Foundation)를 사용하여 작성한 클라이언트 및 서비스가 관련된 시나리오의 경우 OleTransactions 프로토콜을 사용해야 합니다. 단, 타사 프로토콜 스택과의 상호 운용성이 필요한 시나리오의 경우에는 WS-AT(WS-AtomicTransaction) 프로토콜이 유용합니다. 다음 샘플 구성처럼 여러 끝점에 해당 프로토콜별 바인딩을 제공하여 두 가지 프로토콜을 모두 허용하도록 WCF 서비스를 구성할 수 있습니다.
<service name="CalculatorService"> <endpoint address="https://localhost:8000/CalcService" binding="wsHttpBinding" bindingConfiguration="transactionalWsatHttpBinding" contract="ICalculator" name="WSAtomicTransaction_endpoint" /> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
트랜잭션 프로토콜은 transactionProtocol 특성을 사용하여 지정합니다. 그러나 이 바인딩에서는 WS-AT 프로토콜만 사용할 수 있으므로 이 특성은 시스템 제공 wsHttpBinding에 없습니다.
<bindings> <wsHttpBinding> <binding name="transactionalWsatHttpBinding" transactionFlow="true" /> </wsHttpBinding> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
트랜잭션 완료 제어
기본적으로 WCF 작업은 처리되지 않은 예외가 throw되지 않으면 트랜잭션을 자동으로 완료합니다. TransactionAutoComplete 속성 및 SetTransactionComplete 메서드를 사용하면 이 동작을 수정할 수 있습니다. debit 및 credit 연산 등 다른 연산과 동일한 트랜잭션 내에서 연산을 수행해야 하는 경우 다음
Debit
연산 예제에서처럼 TransactionAutoComplete 속성을 false로 설정하여 autocomplete 동작을 비활성화할 수 있습니다. Debit 연산에서 사용하는 트랜잭션은Credit1
연산에서처럼 true로 설정된 TransactionAutoComplete 속성을 가진 메서드가 호출될 때까지 또는Credit2
연산에서처럼 트랜잭션 완료를 명시적으로 표시하기 위해 SetTransactionComplete 메서드가 호출될 때까지 완료되지 않습니다. 설명의 목적으로 두 개의 credit 연산을 예로 들었지만 하나의 credit 연산이 보다 일반적입니다.[ServiceBehavior] public class CalculatorService : IAccount { [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = false)] public void Debit(double n) { // Perform debit operation return; } [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = true)] public void Credit1(double n) { // Perform credit operation return; } [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = false)] public void Credit2(double n) { // Perform alternate credit operation OperationContext.Current.SetTransactionComplete(); return; } }
트랜잭션의 상관 관계를 위해 TransactionAutoComplete 속성을 false로 설정하려면 세션 바인딩을 사용해야 합니다. 이 요구 사항은 ServiceContractAttribute의 SessionMode 속성을 사용하여 지정합니다.
[ServiceContract(SessionMode = SessionMode.Required)] public interface IAccount { [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Debit(double n); [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Credit1(double n); [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Credit2(double n); }
트랜잭션 서비스 인스턴스의 수명 제어
WCF에서는 ReleaseServiceInstanceOnTransactionComplete 속성을 사용하여 트랜잭션 완료 시 기본 서비스 인스턴스를 해제할지 여부를 지정합니다. 이 기본값이 true이기 때문에, 달리 구성하지 않는 한 WCF에서는 효율적이며 예상 가능한 "JIT(Just-In-Time)" 활성화 동작을 수행합니다. 후속 트랜잭션에서의 서비스에 대한 호출은 이전 트랜잭션 상태가 남아 있지 않은 새 서비스 인스턴스가 됩니다. 이는 대개의 경우에 유용하지만 때로는 트랜잭션 완료와 관계없이 서비스 인스턴스 내의 상태를 유지해야 하는 경우가 있습니다. 예를 들면 필요한 상태 또는 리소스에 대한 핸들을 검색하거나 다시 구성하는 데 비용이 많이 드는 경우입니다. ReleaseServiceInstanceOnTransactionComplete 속성을 false로 설정하면 서비스 인스턴스 내의 상태를 유지할 수 있습니다. 이렇게 설정하면 인스턴스 및 그와 관련된 모든 상태를 후속 호출에서 사용할 수 있습니다. 이 설정을 사용하는 경우에는 상태 및 트랜잭션을 지우고 완료하는 시기와 방법에 대해 주의 깊게 고려해야 합니다. 다음 샘플에서는
runningTotal
변수를 사용하여 인스턴스를 유지 관리함으로써 이 작업을 수행하는 방법을 보여 줍니다.[ServiceBehavior(TransactionIsolationLevel = [ServiceBehavior( ReleaseServiceInstanceOnTransactionComplete = false)] public class CalculatorService : ICalculator { double runningTotal = 0; [OperationBehavior(TransactionScopeRequired = true)] public double Add(double n) { // Perform transactional operation RecordToLog(String.Format("Adding {0} to {1}", n, runningTotal)); runningTotal = runningTotal + n; return runningTotal; } [OperationBehavior(TransactionScopeRequired = true)] public double Subtract(double n) { // Perform transactional operation RecordToLog(String.Format("Subtracting {0} from {1}", n, runningTotal)); runningTotal = runningTotal - n; return runningTotal; } private static void RecordToLog(string recordText) { // Database operations omitted for brevity } }
참고
인스턴스 수명이 서비스의 내부 동작이며 ServiceBehaviorAttribute 속성을 통해 제어되므로, 인스턴스 동작을 설정하기 위해 서비스 구성이나 서비스 계약을 수정할 필요가 없습니다. 또한 연결에도 이에 대한 표현이 포함되지 않습니다.