Webサービスにおけるトランザクションは主に2つある。補償(Compensation)トランザクションとアトミック(Atomic)トランザクションである。
補償トランザクションは、トランザクションの補償を何らかのビジネス上の取り決めの下に行うものだ。例えば旅行代理店サービスが航空券予約、宿泊予約、レンタカー予約をまとめて実行する場合、希望のレンタカーが引き当て不能であったら単純にすべてをキャンセルするといったものではなく、航空券予約や宿泊予約は仮予約のうえでレンタカーのみ代替車両を仮引き当てしておき顧客の判断を待ったうえで全体的に予約を確定するといった、場合によっては数日、数週間といった長期レンジ(ロング・トランザクションともいう)でかつ、単純なシステム上のメカニズムで解決しない複雑なビジネス処理を伴ったようなトランザクションであるといえる。
もう1つのアトミック(Atomic)トランザクションは、従来の分散トランザクションの範疇のものであり、ACIDトランザクションを分散アプリケーション間で実現するためのメカニズムである。通常比較的に短時間(ショート・トランザクションともいう)で処理全体が完全に実行されるか、まったく実行されないといった単純なものであるが、それゆえ密結合な部分も多くなる。
Webサービスの拡張仕様でいえば補償トランザクションのための仕様がWS-BusinessActivityであり、アトミック・トランザクションのための仕様がWS-AtomicTransactionである。WCFではアトミック・トランザクションのための実装方法が提供されている。それでは、WCFでいかにしてアトミック・トランザクションを実装していくのかその一端を見ていこう。
■3-1. サービス・サイドでのトランザクションの実装
以下は非常に単純なサービス・サイドのサービス・コントラクトの定義例である。
// サービス・コントラクトの定義
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Add(double n1, double n2);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Subtract(double n1, double n2);
}
TransactionFlow属性(Attribute)は、クライアントからのメソッド呼び出しに対してサービス・サイド側のオペレーションがトランザクション・フローに参加するかどうかを明示的に宣言している部分だ。
この属性のパラメータではTransactionFlowOption.Mandatoryが指定されているが、これにより、クライアントからのトランザクション・フローへの参加が義務付けられている。なおデフォルトでは、TransactionFlowOption.NotAllowedが指定され、トランザクション・フローへの参加は行われない。
しかし、このようにサービス・コントラクトでトランザクション・フローを義務付けたとしても、必ずしも個々のオペレーションがトランザクションに組み込まれるわけではない。それを決めるのは、個々のオペレーションの実装に対するOperationBehavior属性の指定だ。
[ServiceBehavior(TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable)]
public class CalculatorService: ICalculator
{
[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = true)]
public double Add(double n1, double n2)
{
recordToLog(String.Format("Added {0} to {1}", n1, n2));
return n1 + n2;
}
[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = true)]
public double Subtract(double n1, double n2)
{
recordToLog(String.Format("Subtracted {0} from {1}", n1, n2));
return n1 - n2;
}
// ログ追加用のPrivateメソッド
private void recordToLog(string recordText)
{
string fullRecordText =
recordText + " at " + DateTime.Now.TimeOfDay;
using (SqlConnection conn = new SqlConnection(
ConfigurationManager.AppSettings["connectionString"]))
{
SqlCommand cmd = new SqlCommand(
"INSERT into Log (Entry) Values (@Entry)", conn);
cmd.Parameters.AddWithValue("@Entry", fullRecordText);
conn.Open();
cmd.ExecuteNonQuery();
Console.WriteLine("Writing to database: {0}", fullRecordText);
}
}
}
トランザクションの実装に関しては、ServiceBehavior、OperationBehaviorといった属性をサービスの実装クラスおよびメソッドに付与することでサービス内部の振る舞いを規定するようなスタイルとなる。
まずこのコードでは、ServiceBehavior属性をサービスの実装クラスであるCalculatorServiceクラスに付与することによって、トランザクションの分離レベルを設定している。
また、各オペレーション(メソッド)に対しては、OperationBehavior属性を指定することによって、それを自動的にトランザクションに組み込み(TransactionScopeRequired = true)、例外が発生しなければトランザクションをコミットとして自動的に識別する(TransactionAutoComplete = true)ように設定している。
■3-2. クライアント・サイドでのトランザクションの実装
クライアント・サイドでのトランザクションの実装は、System.Transactions名前空間(のクラス群)との非常に透過的なサポートを提供している。下記はクライアント・サイドのコードである。
using (TransactionScope ts = new TransactionScope(
TransactionScopeOption.RequiresNew))
{
Console.WriteLine("Starting transaction");
// Addサービス・オペレーションの呼び出し
double value1 = 100.00D;
double value2 = 15.99D;
double result = proxy.Add(value1, value2);
Console.WriteLine(" Add({0},{1}) = {2}", value1, value2, result);
// Subtractサービス・オペレーションの呼び出し
value1 = 145.00D;
value2 = 76.54D;
result = proxy.Subtract(value1, value2);
Console.WriteLine(" Subtract({0},{1}) = {2}", value1, value2, result);
Console.WriteLine("{0} transaction\n",
commitTransaction ? "Committing" : "Rolling back");
if (commitTransaction)
ts.Complete();
}
}
ここではTransactionScopeクラス(System.Transactions名前空間)のコンストラクタのパラメータに「TransactionScopeOption.RequiresNew」を指定して新たなトランザクションを開始し、該当トランザクション・スコープ内でサービス・サイドの複数のサービス・オペレーション(=proxyオブジェクトのAddメソッド、Subtractメソッドの呼び出し)を実行している。これらは1つのアトミック・トランザクションとして管理され、すべてが正常終了したならば自動的にコミットされるようなコーディングとなっている。
ここで重要なのはトランザクション・スコープ内のトランザクションは、ローカルDBアクセス、分散DBアクセス、WS-AtomicTransaction経由の分散Webサービスであったとしてもプログラミング上はまったく透過で非常にシンプルに実装可能となっている点である。
また、下記のコードはクライアント・サイドのコンフィグレーション・ファイルの内容である。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint name=" Endpoint1"
address="http://localhost:8000/ServiceModelSamples/service"
binding="wsHttpBinding"
bindingConfiguration="wsHttpBindingWSAT"
contract="Microsoft.ServiceModel.Samples.ICalculatorLog" />
</client>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingWSAT"
transactionFlow="true" />
</wsHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
この例ではバインディングとしてwsHttpBindingを使用しているが、そこで「transactionFlow="true"」の指定によりトランザクションのフローを有効化することが定義されていることが分かる。
以上でWCFでの、セキュアでリライアブルでトランザクティッドな分散アプリケーションの構築に関して大まかにその概要を説明した。
セキュリティおよびリライアブルに関しては、ほぼコンフィグレーションで詳細な設定のカスタマイズが可能であるが、分散トランザクションに関してはサービスおよびクライアントの内部実装への影響が多いため、宣言個所もサービス・コントラクトのみならずサービス実装クラスにも存在し単純にコンフィグレーションのみではないことが分かる。
なお、以上見てきたものはWS-*と呼ばれるWebサービス拡張仕様群の中の主要な仕様へのWCFでの実装方法を見てきたわけであるが、参考までに、WS-*各種仕様のWCFでの対応に関して下図に掲載しておく。
ここまで見てきたところで再度、WCFのアーキテクチャを眺めてみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.