Delphi Labs: DataSnap XE - サーバーメソッドのライフサイクル

By: Chikako Yonezawa

Abstract: このラボでは、Delphi XE を使用し、DataSnap サーバーのメソッドクラスのインスタンスのライフサイクル管理のためのさまざまなオプションを探索します。

    はじめに

DataSnap のスケーラビリティの鍵は、サーバーメソッドインスタンスのライフサイクルの管理にあります。DataSnap アーキテクチャの中で、クライアントは、いわゆる「サーバーメソッドクラス」の公開メソッドをリモートで呼び出します。プログラマとしては、サーバークラスのインスタンスの作成と破棄について心配する必要はありません。サーバークラスの型は何か、ライフサイクルのポリシーは何かを伝えることのみ必要です。デフォルトのサーバーメソッドのライフサイクルは “session” です。これは接続してているクライアントごとに、クライアントが接続した際に作成され、それが切断された際に破棄されるひとつの専用のサーバーメソッドインスタンスを意味しています。これは、Enterprise Java Bean (EJBs) の「ステートフル」の概念に似ています。ライフサイクル用の他の可能なオプションは “server” です。このシナリオで、すべてのクライアントは、同じサーバークラスのインスタンスのメソッドを呼び出しています。同時に複数のスレッドから呼び出される可能性があるメソッドですので、“server” サーバーメソッドの実装がスレッドセーフであることを確認します。サーバーメソッドクラスの最後のライフサイクルオプションは、“invocation” です。 これば、おそらく最もスケーラビリティの高い可能性があります。なぜなら、サーバーメソッドのクラスインスタンスは、メソッドの呼び出しの間だけ作成されるからです。リクエストが到着する前にインスタンス化され、呼び出しが完了したときに破棄されます。これは、EJB の「ステートレス」と概念的にかなり似ています。

このラボでは、Delphi XE を使用して、サーバーとクライアントアプリケーションで構成される簡単なサーバーメソッドのライフサイクルデモを行います。最初に、接続したクライアントそれぞれがサーバー内部に専用のサーバーメソッドクラスのインスタンスを持つことをデフォルトのシナリオで確認します。デモの2番目のパートでは、2つの他のライフサイクルオプションである “server” と “invocation” を試します。

    “Server” および “Client” プロジェクトの作成

DataSnap サーバーアプリケーションの構築から始めましょう。

メニューの [ファイル|新規作成|その他] をクリックし、「新規作成」ダイアログで、Delphi DataSnap サーバーアプリケーションを作成するために、Delphi プロジェクトの「DataSnap Server カテゴリ」内の「DataSnap Server」ウィザードをダブルクリックします。

Hide image
DataSnap01

ウィザードの最初のタブでは、プロジェクトの種類としてデフォルトの「VCL フォームアプリケーション」のままにします。そして、2番目のタブでは、サンプルメソッドオプションのチェックを外します。

Hide image
Click to see full-sized image

expand view>>

ウィザードの他の全てはデフォルトを選び、[完了] をクリックします。

プロジェクトを保存します。メニューより [ファイル|すべて保存] を選択します。任意の場所に保存することも、別の名前で保存することもできます。ここでは、 “C:\DataSnapLabs\ServerClassesLifecycle” フォルダを作成し、すべてのファイルを保存します。保存される最初のファイルは、アプリケーションのメインフォームであるユニットで、“FormServerUnit” と命名します。サーバーメソッドクラスのユニット、および、サーバーコンテナのユニットはデフォルトの名前のままにします。全体のプロジェクトには、“LifecycleServer” と命名しました。繰り返しますが、これは何でもOKです。

このデモ全体として、画面上に複数のウィンドウが表示される予定です。サーバーフォームのキャプションを “Lifecycle Server” とリネームし、フォーム上のユーザーインターフェースコンポーネントが見えない最小の大きさにします。Web アプリケーション、または、コンソールアプリケーションのような、サーバーアプリケーションのほとんどのタイプは、ウィンドウを持ちません。

サーバーにクライアントアプリケーションを追加しましょう。プロジェクトマネージャの先頭のノード (ProjectGroup の部分の) 上で、マウスの右ボタンをクリックし、[新規プロジェクトを追加] を選択します。「新規作成」ダイアログで、Delphi プロジェクトカテゴリ内の VCL フォームアプリケーションを選択し、[OK]をクリックします。すべてを保存します。メインのアプリケーションフォームには “FormClientUnit”、プロジェクトには “LifecycleClient”、プロジェクトグループには、 “Lifecycle” と命名して保存します。

サーバープロジェクトに戻りましょう。IDE 内でアクティブなプロジェクトはいつでも1つだけです。サーバープロジェクトが、IDE でアクティブであることを確認します。プロジェクトをアクティブにする最も簡単な方法は、プロジェクトマネージャ上の名前の上でダブルクリックすることです。アクティブなプロジェクトは、プロジェクトマネージャ内で太字で表示され、その名前が、IDE ウィンドウの上部に表示されているのを確認することができます。

Hide image
DataSnap51

    サーバーメソッドのクラスインスタンス識別を実装する

DataSnap ウィザードでは、サーバークラスにサンプルメソッドは追加しませんでした。完全に空で、どのメンバも所持していません。DataSnap アーキテクチャのクライアントインタフェースの定義は、とても単純です。サーバークラスの実装で、public、または published で定義されたいずれのメソッドも、自動的にクライアントから利用できるようになります。これは、最も強力な DataSnap 機能の一つで、DataSnap を非常に動的なものにしています。他の分散テクノロジのクライアント開発を行うには、通常、サーバーインターフェースが記載されているドキュメント、または、呼び出し可能なメソッド名、パラメータ型、名前、戻り値、などの情報を取得するためのいくつかの他の方法が必要です。DCOM にはタイプライブラリの概念が、CORBAには IDL の概念が、SOAP Web サービスには WSDL の概念があります。DataSnap クライアント/サーバー開発は、概念的に、実行中の Web サービスアプリケーションから SOAP サービスプロキシを生成し、実行時に WSDL を取得する “\wsdl” パスを供給することに近いです。DataSnap で必要なのは、実行中のDataSnapサーバーのインスタンスにアクセスすることだけです。それだけです。実行時にすべての必要な情報を取得します。

DataSnap サーバーの設計時には、常に、テスト用クライアントアプリケーションを作成し、サーバー実装と同期させることをお勧めします。この方法により、テストクライアントの実装作業を最後の最後まで残しておく必要はありません。サーバーメソッドの実装が時間とともに変化しても、クライアントプロキシの再生成を行うだけでよいので、サーバーとクライアントのプロジェクトは同期されます。

デモの目的のため、一意の識別子を返却する public な “GetId”メソッドの実装を、サーバーメソッドクラスに追加します。この方法で、クライアントアプリケーションは、サーバー内部のどのサーバーメソッドクラスのインスタンスと通信しているのかを確認できます。

Globally Unique Identifiers (GUID) を生成する OS の機能を基にした呼び出しを行うひとつのグローバル関数を供給する、小さなユーティリティユニットを導入しました。

メニューより [ファイル|新規作成|ユニット] を選択します。そのユニットを “GUID_utils” として保存し、その中身を以下のコードに書き換えます:

unit GUID_utils;

interface

function GetNewGUID: string;

implementation

uses
  ActiveX, SysUtils;

function GetNewGUID: string;
var aGUID: TGUID;
begin
  CoCreateGUID(aGUID);
  Result := GUIDToString(aGUID);
end;

end.

サーバーメソッドクラスの実装内で、この機能を使用します。

“ServerMethodsUnit2”(番号は異なるかもしれません) に切り替えて、以下のコードに書き換えます:

unit ServerMethodsUnit2;

interface

uses
  SysUtils, Classes, DSServer;

type
{$METHODINFO ON}
  TServerMethods2 = class(TComponent)
  private
    FID: string;
  public
    constructor Create(AOwner: TComponent); override;
    function GetID: string;
  end;
{$METHODINFO OFF}

implementation

uses GUID_utils;

{ TServerMethods2 }

constructor TServerMethods2.Create(AOwner: TComponent);
begin
  inherited;
  FID := GetNewGUID;
end;

function TServerMethods2.GetID: string;
begin
  Result := FID;
end;

end.

サーバーメソッドクラスには、ひとつの private な “FID” という string 型のフィールド、“GUID” を使用して “FID” を初期化するオーバライドされたコンストラクタ、引数をもたず、FID フィールドに保存されている値を返却する public なメソッドが含まれています。

それでは、DataSnap は、“TServerMethods2” クラスが「サーバーメソッド」で、そのパブリックメソッドがクライアントに公開される必要のあることをどのように知るのでしょうか?それは中心となる “TDSServer” コンポーネントと、サーバーメソッドクラスとの間のリンクとして動作する特別な “TDSServerClass” コンポーネントのロールです。DataSnap サーバーアプリケーションには、常に、ひとつの “TDSServer” コンポーネントと、いくつかの トランスポートコンポーネントがあり、“TDSServer” はそれを通して、クライアントや、プログラマが、クライアントからの要望があった際にインスタンス化される必要があるサーバーメソッドの型の値を割り当てることができる “OnGetClass” イベントを提供する “TDSServerClass” コンポーネントと通信します。

Hide image
DataSnap52

DataSnap サーバーウィザードは、常に、 “DSServerClass.OnGetClass” イベントの実装を生成します。

procedure TServerContainer2.DSServerClass1GetClass(
  DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := ServerMethodsUnit2.TServerMethods2;
end;

“OnGetClass” の実装の中で認識すべき最も重要な部分は、プログラマが、参照する型を割り当てるということであり、インスタンスのパラメータへ参照を行わない事です。DataSnap アーキテクチャで、プログラマは、サーバークラスのライフサイクルを直接コントロールしません。“TDSServerClass” コンポーネントに、ライフタイムを宣言して管理する “LifeCycle” プロパティがあります。“DSServerClass1.LifeCycle” プロパティのデフォルト値は “Session”です。

Hide image
DataSnap53

クライアントの実装を行うためにサーバーのインスタンスを動作しておく必要があるので、サーバーアプリケーションをコンパイルし、実行します。

すべてのプロジェクトをビルドします。IDE 内で、サーバープロジェクトがアクティブであることを確認し、デバッガを使わずにサーバープロジェクトを実行するために、緑色の矢印をクリックします。サーバーアプリケーションのウィンドウは最小化しておきます。

    “Session” ライフサイクルをテストする

プロジェクトマネージャの “LifecycleClient” プロジェクトをダブルクリックして、アクティブにします。

クライアントフォームを開き、フォームに、1つのボタンと、1つのラベルを追加します。通常、フォームにコンポーネントを追加する最も簡単な方法は、「IDE インサイト」を使用することです。F6 (または “Ctrl+.”) を押し、選択したいコンポーネントの名前をタイプします。ボタンのキャプションを “Get ID” に変更します。

フォームに “TSQLConnection” コンポーネントを追加します。

“TSQLConnection” は、dbExpress ドライバへの接続を提供する非ビジュアルコンポーネントです。dbExpress フレームワークは、Delphi 2007 において純粋なDelphi コードで完全に再設計されています。Delphi 2009 で、DataSnap サーバーへの接続は、RDBMS サーバーではなく DataSnap サーバーへの接続を提供する DBX4 ドライバの形態で実装されました。クライアントの視点からは、DataSnap サーバーのインスタンスは、データベースのインスタンスのように見え、サーバーメソッドは、まさにストアドプロシージャのように見えます。

フォーム上の “TSQLConnection1” コンポーネントを選択します。“Driver” プロパティを “DataSnap” に変更します。これにより、オブジェクトインスペクタ内の “Driver” プロパティを展開し、DataSnap 固有のプロパティの設定を行うことができます。

Hide image
Click to see full-sized image

expand view>>

クライアントの起動時に自動的なログインフォームの表示を回避するために、“SQLConnection1.LoginPrompt” を “False” に設定するすることをお勧めします。

サーバーが起動中で、すべて正しく設定されているのであれば、クライアントの設計時に起動中のサーバーに接続するために、“Connected” プロパティを “True” に設定できるはずです。

次の手順は、すべてのサーバーメソッドのシグネチャを含んだサーバーのメタデータから自動的に生成されたソースコードを含むクライアントプロキシユニットを生成することです。

“SQLConnection1” コンポーネント上で、マウスの右ボタンをクリックし、表示されたコンテキストメニューから [DataSnap クライアントクラスの生成] を選択します。

Hide image
DataSnap55

Hide image
client form initially

このコマンドは、クライアントアプリケーションで使用される DataSnap クライアントクラスを含むユニットを生成します。このユニットを “proxy” と命名して保存します。

// 
// Created by the DataSnap proxy generator.
// 2/14/2011 10:39:48 PM
// 

unit proxy;

interface

uses DBXCommon, DBXClient, DBXJSON, DSProxy, Classes, SysUtils, DB, SqlExpr, DBXDBReaders, DBXJSONReflect;

type
  TServerMethods2Client = class(TDSAdminClient)
  private
    FGetIDCommand: TDBXCommand;
  public
    constructor Create(ADBXConnection: TDBXConnection); overload;
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function GetID: string;
  end;

implementation

function TServerMethods2Client.GetID: string;
begin
  if FGetIDCommand = nil then
  begin
    FGetIDCommand := FDBXConnection.CreateCommand;
    FGetIDCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FGetIDCommand.Text := 'TServerMethods2.GetID';
    FGetIDCommand.Prepare;
  end;
  FGetIDCommand.ExecuteUpdate;
  Result := FGetIDCommand.Parameters[0].Value.GetWideString;
end;


constructor TServerMethods2Client.Create(ADBXConnection: TDBXConnection);
begin
  inherited Create(ADBXConnection);
end;


constructor TServerMethods2Client.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean);
begin
  inherited Create(ADBXConnection, AInstanceOwner);
end;


destructor TServerMethods2Client.Destroy;
begin
  FreeAndNil(FGetIDCommand);
  inherited;
end;

end.

クライアントアプリケーションからサーバーメソッドを呼び出すためには、サーバークラス名に “Client” を追加したのと同じ名前をもつ生成されたクライアントクラスをインスタンス化する必要があります。

実際にサーバーメソッドへの接続を提供するものは、フォーム上の “SQLConnection1” コンポーネントです。そのため、クライアントクラスのコンストラクタは、 “TSQLConnection” クラスのプロパティである “DBXConnection” パラメータを受け取り、それはまた、下位のコンポーネントレイヤーにおいて、サーバーへ実際の接続を実装する基になるクラスです。

メインのクライアントフォームに切り替え、IDE のメニューの [ファイル|ユニットを使う] を選択し、フォームユニットの “uses” 句に、新しく生成された proxy ユニットを追加します。

サーバーメソッドを呼び出すために、クライアントクラスをインスタンス化する必要があります。ローカルメソッドのようにサーバーメソッドを呼び出すことができます。

フォーム上のボタンコンポーネントをダブルクリックし、プロキシを作成し、その “GetID” メソッドを呼び出し、ラベルコンポーネントに返却された値を表示し、クライアントプロキシを解放する以下の “OnClick” イベントを実装します。

procedure TForm3.Button1Click(Sender: TObject);
var aClient: TServerMethods2Client;
begin
  aClient := TServerMethods2Client.Create(SQLConnection1.DBXConnection);
  try
    Label1.Caption := aClient.GetID;
  finally
    aClient.Free;
  end;
end;

[ファイル|すべて保存] を行い、緑色の矢印のアイコンをクリックして、デバッガを使わずにクライアントアプリケーションを実行します。

ボタンをクリックすると、リモート DataSnap サーバーアプリケーションの中で動作しているサーバーメソッドクラスのインスタンスの一意なグローバル識別子を確認することができます。

何度かボタンをクリックした場合でも、識別子が変化しないことが分かるはずです。

クライアントアプリケーションの別のインスタンスを実行するために、再び、IDE の緑色の矢印のアイコンを押します。ボタンをクリックすると、両方のクライアントアプリケーションが、同じサーバクラスの異なるインスタンスを使って通信していることを、識別子が異なっていることで確認できるでしょう。

Hide image
Click to see full-sized image

expand view>>

これは、DataSnap サーバーのデフォルトの動作です。サーバーメソッドクラスのひとつのインスタンスが、クライアントごとに、サーバーの接続時に作成され、切断時に破棄されます。

    “Server” および “Invocation” ライフサイクルをテストする

サーバーとクライアントプロジェクトを拡張し、“TDSServerClass.LifeCycle” プロパティのためのすべての他の可能な値をテストするには?

すべてのクライアントプログラムとサーバープログラムを終了します。

ServerContainerUnit に 2つの “TDSServerClass” コンポーネントを追加することから始めます。“LifeCycle” のすべての可能な値を使う 3つのサーバークラスのコンポーネントが揃います。

フォーム上の “DSServerClass1” コンポーネントを選択し、オブジェクトインスペクタに行き、“Name” プロパティを “DSServerClass_Session” に変更します。

デザイナでコンポーネントが選択されているのを確認し、Ctrl-C を押し、そして、2つの追加サーバークラスコンポーネントを ServerContainerUnit に追加するために 2回 Ctrl-V を押します。

これらのコンポーネントを、“DSServerClass_Server” と、“DSServerClass_Invocation” にリネームし、コンポーネントの名前が、ライフサイクルの設定を反映するように “Lifecycle” プロパティを変更し、確認します。

Hide image
server container

この時点で、サーバープロジェクトを実行すると、「TServerMethods2 クラスは、既にサーバーメソッドリストに追加されています」というエラーを取得します。

DataSnap アーキテクチャはこのように設計されています。他の方法はありません。異なる “TDSServerClass” コンポーネントは、 “OnGetClass” イベントで、異なるクラス参照を返却しなければなりません。複数の “TDSServerClass” コンポーネントのための同じサーバーメソッドクラスの実装を直接使用することはできません。

このデモを明確にするためには、分かり易いサーバーメソッドのクラス名が必要です。

プロジェクトマネージャー上で、サーバープロジェクト内の ServerMethodsUnit を選択し、マウスの右ボタンを押して表示されるメニューより [名前の変更] を選択し、“ServerMethodsUnitSession” と名前を変更します。その際、コード内のサーバークラスの名前を “TServerMethodsSession” に変更します。

サーバークラス名を変更して、サーバークラスの機能の複製を行います。

プロジェクトに新しいユニットを追加します。“ServerMethodsUnitServer” と命名します。

サーバーメソッドクラスの実装を “ServerMethodsUnitSession” から “ServerMethodsUnitServer” へコピーして置き換えますが、ユニット名は元のままにしておきます。

プロジェクトに新しいユニットを追加します。“ServerMethodsUnitInvocation” と命名します。

同様に、サーバーメソッドクラスの実装をコピーして置き換えますが、ユニット名は元のままにしておきます。

この時点で、プロジェクトマネージャは次のようになります:

Hide image
DataSnap56

最後の仕事は、3つの “DSServerClass” コンポーネントすべてに “OnGetClass” イベントを正しく実装することです。ServerContainerUnit に、すべての新しいユニットを追加します。

サーバークラスコンポーネントをコピーしましたので、それらの3つすべてが同じ “OnGetClass” 実装を指しています。もし、異なるイベントハンドラを供給したいのであれば、オブジェクトインスペクタの「イベント」タブで、現在選択されているものをクリアし、ダブルクリックして新しい空の実装を生成します。

ServerContainerUnit の実装部は、以下のようになっているはずです:

// …

implementation

uses
  ServerMethodsUnitSession,ServerMethodsUnitInvocation,
  ServerMethodsUnitServer;

{$R *.dfm}

procedure TServerContainer2.DSServerClassInvocationGetClass(
  DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := TServerMethodsInvocation;
end;

procedure TServerContainer2.DSServerClass_ServerGetClass(
  DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := TServerMethodsServer;
end;

procedure TServerContainer2.DSServerClass_SessionGetClass(
  DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := TServerMethodsSession;
end;

end.

サーバーの準備が整いました。すべてのプロジェクトをビルドして、デバッガを使わずに実行します。

最後に行うことは、クライアントを拡張することです。

クライアントプロジェクトに切り替えます。“SQLConnection1” コンポーネントを選択して “Connected” プロパティが “true” でにあれば、“false” に設定します。そして、それからサーバーの最新バージョンに接続されることを確認するために “true” に戻します。

“SQLConnection1” コンポーネント上で、マウスの右ボタンをクリックし、プロキシのソースコードを再生成するために、コンテキストメニューから[DataSnap クライアントクラスの生成] を選択します。“proxy” ユニットを更新し、保存します。すべてのサーバメソッドクラスに対応するクライアントクラスが生成されることに注意してください。1つのクライアントプロキシクラスが 3つのクライアントプロキシクラスに再生成されています。

クライアントアプリケーションのメインフォームに切り替えます。2つのボタンと2つのラベルを追加し、それぞれのボタンから異なるサーバークラスを呼ぶコードと、ラベルに返却された値を表示するコードを追加します。

procedure TForm3.ButtonInvocationClick(Sender: TObject);
var aClient: TServerMethodsInvocationClient;
begin
  aClient := TServerMethodsInvocationClient.Create(
    SQLConnection1.DBXConnection);
  try
    LabelInvocation.Caption := aClient.GetID;
  finally
    aClient.Free;
  end;
end;

procedure TForm3.ButtonServerClick(Sender: TObject);
var aClient: TServerMethodsServerClient;
begin
  aClient := TServerMethodsServerClient.Create(
    SQLConnection1.DBXConnection);
  try
    LabelServer.Caption := aClient.GetID;
  finally
    aClient.Free;
  end;
end;

procedure TForm3.ButtonSessionClick(Sender: TObject);
var aClient: TServerMethodsSessionClient;
begin
  aClient := TServerMethodsSessionClient.Create(
    SQLConnection1.DBXConnection);
  try
    LabelSession.Caption := aClient.GetID;
  finally
    aClient.Free;
  end;
end;

デバッガを使用せずにクライアントプロジェクトの2つのインスタンスを実行すると、以下のことを確認できるはずです:

  • 両方のクライアントは、変化しない同じ “server lifecycle” インスタンスに接続されます。
  • クライアントごとに異なるセッションのインスタンスがあり、それらは変化しません。
  • [Invocation] ボタンをクリックするたびに、異なる識別子を得ます。要求がリクエストされると常に異なるサーバーメソッドクラスのインスタンスがあるという意味です。

これが、まさに期待していたものです!

Hide image
Click to see full-sized image

expand view>>

    まとめ

この “Delphi Labs” 記事内で、DataSnap XE サーバーメソッドのクラスインスタンスのライフサイクルのさまざまなオプションを見てきました。

この例の中で、TCP/IP で通信するテストサーバーとクライアントを構築しました。それぞれのサーバーメソッドのインスタンスは、コンストラクタの中で異なる GUID が割り当てられています。“GetId” メソッドを使用することで生成された ID が返却され、クライアントアプリケーションは、どのサーバークラスのインスタンスと通信しているのかをチェックすることができました。

この記事のソースコードは、Code Central (http://cc.embarcadero.com/item/28199) にあります。そして、この記事のビデオ版は YouTube (http://www.youtube.com/watch?v=VI_o6bwIfkM および http://www.youtube.com/watch?v=XcLOm-v7Ing) で公開しています。

"Delphi Labs" に関する詳細情報は、blog (http://blogs.embarcadero.com/pawelglowacki) で公開しています。

Server Response from: ETNASC04