Delphi Labs: DataSnap XE - Server Methods Lifecycle

By: Pawel Glowacki

Abstract: In this lab exercise we are going to use Delphi XE to explore different options for DataSnap server methods class instance lifecycle management

    Introduction

The key to DataSnap scalability is server methods instances lifecycle management. In the DataSnap architecture clients are remotely invoking public methods of so called “server methods classes”. As a programmer you do not need to worry about creating and destroying server class instances. You only need to tell what the type of a server class is and what its lifecycle policy is. By default server methods lifecycle is “session”, which means that for every connected client there is one dedicated server methods instance that is created when client connects and destroyed when it disconnects. This is quite similar to the concept of a “stateful” Enterprise Java Bean (EJBs). The other possible option for lifecycle is “server”. In this scenario all clients are invoking methods of the same server class instance. Make sure that your implementation of the “server” server methods instance is thread-safe, as its methods might be called from multiple threads simultaneously. The last “lifecycle” option for server method classes is “invocation”. This is probably the most scalable possibility, because server methods class instance is created just for the duration of a method call. It is instantiated just before the request arrives and destroyed when the call is completed. This is quite similar conceptually to “stateless” EJBs.

In this lab exercise we are going to use Delphi XE to build a simple server methods lifecycle demo consisting of server and client applications. First we are going to verify that default scenario where each connected client has its own server methods class instance inside the server. In the second part of the demo we are going to also try two other lifecycle options – “server” and “invocation”.

    Creating “Server” and “Client” projects

Let’s start from building a DataSnap server application.

Click on the “File – New – Other” menu and inside the “New Items” dialog double-click on the “DataSnap Server” wizard in “Delphi Projects – DataSnap Server” category to create a standalone Delphi DataSnap server application.

Hide image
DataSnapServerWiz

In the first tab of the wizard keep the default project type “VCL Forms Application” and on the second tab uncheck generating sample methods options.

Hide image
Click to see full-sized image

Keep all other default choices in the wizard and click on “Finish”.

expand view>>

Now it is a good moment to save the project. Select “File – Save All”. You can save your files wherever you want and give them any names. For all files in this episode I have created “C:\DataSnapLabs\ServerClassesLifecycle” folder. The first file to save is the unit with the application main form so I name it “FormServerUnit”. Keep the default names for server methods classes unit and for the server container unit. I have named the whole project as “LifecycleServer”. Again – this could be anything.

As I am going to have multiple windows on the screen throughout this demo, I’m going to rename the server’s form caption to “Lifecycle Server” and minimize its size slightly as there is not going to be any user interface components on the form. Most server application types, like web apps or console app, do not have any window…

Now let’s quickly add a client application to our server. Right-click in the Project Manager on the top node – which is a project group – and select “Add New Project”. In the “New Items” dialog select Delphi VCL Forms application and click on OK. Save All. Save the main application form as “FormClientUnit”, project as “LifecycleClient” and the project group as just “Lifecycle”.

OK. Let’s return to the server project. At any time there could be only one active project in the IDE. Make sure that the server project is active in the IDE. The easiest way to make a project active is to double-click on its name in the Project Manager. The active project is displayed in bold in the Project Manager and also you can see that its name is displayed at the top of the IDE window.

Hide image
Initial PM view

    Implementing Server Methods Class Instance Identification

In the “DataSnap Wizard” we have decided not to add any sample methods to our server class, so it is now completely empty and does not have any members. In the DataSnap architecture defining the client interface is very simple. In the server class implementation any methods that are defined public or published are automatically made available to clients. This is one of the strongest DataSnap features and makes it very dynamic. In order to develop a client in other distributed technologies you typically need a document describing server interface or some other way of obtaining information what are the callable method names, what are the parameter types, names, return values and so on. In DCOM there is a concept of type library, in CORBA there is an IDL and in SOAP Web Services there a WSDL. DataSnap client/server development is conceptually close to generating soap services proxies from running web services application and providing the “\wsdl” path to retrieve the WSDL at runtime. In DataSnap you just need to have access to a running instance of the DataSnap server. That’s it. You retrieve all necessary information at runtime.

During the design of your DataSnap server it is always a good idea to have a test client application and evolve it with the server. In this way you do not have to leave the task of implementing a test client to the very last minute. When the implementation of your server methods change over the time, you just need to regenerate client proxies, so server and client projects are in sync.

For demonstration purposes let’s add to the server methods class implementation a “GetId” public method returning a unique identifier. In this way a client application can check with which server methods class instance inside the server it communicates with.

I have put together a little utility unit that just provides one global function that calls into the underlying OS functionality of generating Globally Unique Identifiers (GUID).

Select “File – New – Unit”. Save the new unit as “GUID_utils” and replace its contents with the following code:

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.

We are going to use this functionality in the implementation of our server methods class.

Switch to “ServerMethodsUnit2” (whatever your number might be) and replace it with the following code:

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.

The server methods class contains one private string field called “FID”, an overridden constructor where we initialize the “FID” with a GUID and a public method not taking any arguments and just returning the value stored in FID field.

OK. So how the DataSnap knows that “TServerMethods2” class is our “server method” and its public methods need to be exposed to clients? This is the role of a special “TDSServerClass” component that acts as a link between the main “TDSServer” component and any server methods class. In one DataSnap server application there is always one “TDSServer” component, a number of transport components, through which “TDSServer” communicates with clients and a number of “TDSServerClass” components that provides the “OnGetClass” event where the programmer can assign value to a type of a server methods class that needs to be instantiated when client requests arrive.

Hide image
ServerClass events in Obj Insp

DataSnap server wizards always generate the implementation of the “DSServerClass.OnGetClass” event.

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

The most important part to realize in the implementation of the “OnGetClass” event is the fact that programmer is assigning a type reference and not an instance reference to the parameter. In the DataSnap architecture a programmer is not directly controlling the lifecycle of the server class. There is a “Lifecycle” property on the “TDSServerClass” component that manages the lifetime declaratively. The default value for “DSServerClass1.LifeCycle” property is “Session”.

Hide image
ServerClass props in Obj Insp

Now it is time to compile and run server application, because we need a running server instance in order to be able to implement the client.

Build all projects. Make sure that the server project is active in the IDE and click on a green arrow to run the server project without debugging. Minimize the server application window.

    Testing “Session” Lifecycle

Double-click on the “LifecycleClient” project in the Project Manager to make it active.

Open the client form and add a button and a label to the form. Probably the easiest way to add components to the form is with the “IDE Insight”. Just press F6 (or “Ctrl+.”) and start typing the name of the component you want to select. Change the button’s caption to “Get ID”.

Add a “TSQLConnection” component to the form.

The “TSQLConnection” is a non-visual component that provides connectivity to DBExpress drivers. The DBExpress framework has been completely re-architected in pure Delphi code in Delphi 2007. In Delphi 2009 the connectivity to DataSnap servers has been implemented in the form of a DBX4 driver that provides connectivity not to an RDBMS server but to a DataSnap server. From the client perspective a DataSnap server instance looks very similar to a database instance and server methods look very much like stored procedures.

Select “TSQLConnection1” component on the form. Modify its “Driver” property to “DataSnap”. Notice that now you can expand “Driver” property in the Object Inspector and configure properties that are specific to DataSnap.

Hide image
Click to see full-sized image

expand view>>

It is a good idea to set the “SQLConnection1.LoginPrompt” to “False” to avoid displaying the automatic login form at client startup.

If the server is running and everything is configured correctly you should be able to set the “Connected” property to “True” in order to connect to the running server at client design-time.

The next step is to generate client proxy unit which contains automatically generated source code from server metadata that mimics signatures of all server methods.

Right-click on the “SQLConnection1” component and select “Generate DataSnap Client Classes” from the context menu.

Hide image
client form initially

This command generates a new unit with DataSnap client classes to be used in a client application. Save this unit as “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.

In order to call server methods from a client application, you need to instantiate a generated client class that has the same name as the server class with “Client” appended to it.

What really provides connectivity to the server methods is the “SQLConnection1” component on the form. That is why the client class constructor takes as a parameter a “DBXConnection” parameter, which is a property of “TSQLConnection” class and also the real underlying class implementing the actual connectivity to the server in the underlying component-agnostic layer.

Switch to the main client form and from the “File” menu select “Use Unit” to add newly generated proxy unit to the “uses” clause of the form’s unit.

In order to call a server method you need to instantiate a client class and you can call server methods like if they were a local methods.

Double-click on the button component on the form and implement the following “OnClick event”, which creates a proxy, calls its “GetId” method, displays received value in the label component and then frees the client proxy.

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

“Save All” and run the client application without debugging by clicking on the green arrow icon.

If you click on the button you should see the unique global identifier of a server methods class instance running inside a remote DataSnap server application.

If you click on the button more times you should see that the identifier does not change.

Now press the green arrow icon in the IDE again to start another instance of a client application. If you click on the button you will see that the identifier is different, which means that both client applications are communicating with different instances of the same server class.

Hide image
Click to see full-sized image

expand view>>

This is the default behavior of DataSnap servers. One instance of a server methods class is created for every client when it connects to the server and destroyed when it disconnects.

    Testing “Server” and “Invocation” Lifecycle

What about extending our server and client projects and testing all other possible values for the “TDSServerClass.LifeCycle” property?

Close all client programs and the server program.

We are going to start from adding two more “TDSServerClass” components to the server, so there will be three server class components with all possible values for “LifeCycle”.

Select the “DSServerClass1” component on the form, go to the Object Inspector and change the “Name” property to “DSServerClass_Session”.

Make sure that the component is selected in the designer; press Ctrl-C and two times Ctrl-V to add to the container unit two additional server class components.

Make sure to rename these components to “DSServerClass_Server” and “DSServerClass_Invocation” and also change their “Lifecycle” properties so the name of the component reflects its lifecycle setting.

Hide image
server container

If you try to run the server project at this stage you will get an error saying that the “DSServerMethods2” class has already been added to the server methods list.

This is how the DataSnap architecture has been designed. There is no other way. Different “TDSServerClass” components must return in their “OnGetClass” events references to different classes. You just cannot directly use the same server methods class implementation for multiple “TDSServerClass” components.

To make sure that this demonstration is clear, we do not want any of the server methods class names to stand out.

Go to the server methods class unit and save it as “ServerMethodsUnitSession” and change the name of the server class to “TServerMethodsSession”.

Now we are going to replicate the functionality of the server class just by changing the server class names.

Add to the project a new unit. Name it “ServerMethodsUnitServer”.

Copy and replace the implementation of the server methods class from the “ServerMethodsUnitSession” to “ServerMethodsUnitServer” preserving the unit name.

Add to the project a new unit. Name it “ServerMethodsUnitInvocation”.

Copy and replace the implementation of the server methods class preserving the unit name.

At this stage my project manager looks like this:

Hide image
final server

The last task is to properly implement “OnGetClass” events for all three “DSServerClass” components. Add all new units to the server container.

Because we were coping the server class components all three of them point to the same “OnGetClass” implementation. If you want to provide a different event handler just clear the current selection in the “Events” tab of the Object Inspector and double-click to generate a new empty implementation.

The implementation section of the container unit should look like the following:

// …

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.

Now the server is ready. Build all projects and run the server without debugging.

The last thing is to extend the client.

Switch to the client project. Select the “SQLConnection1” component and if its “Connected” property is still set to “true”, set it to “false” and then back to “true” to make sure we are connected with the latest version of the server.

Right click on the “SQLConnection1” component and select “Generate DataSnap Client Classes” from the context menu to regenerate proxy source code. Save modified “proxy” unit. Notice that for every server methods class there is a corresponding client class generated, so now instead of just one client proxy class we have three.

Switch to client application main form. Add two additional buttons and two labels and add code to call different server classes from different buttons and display results in labels.

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;

When you now run two instances of the client project without the debugging you should be able to verify that:

  • Both clients are connected to the same “server lifecycle” instance, which does not change;
  • There are different session instances for each client and they do not change;
  • Every time you click on the “invocation” button you get different identifier, which means that there is always a different server methods class instance servicing requests.

This is exactly what we were expecting!

Hide image
Click to see full-sized image

expand view>>

    Summary

In this “Delphi Labs” episode we have looked into different options for DataSnap XE server methods class instances lifecycle.

In this example we have built a test server and a client that communicates over the TCP/IP. Each server methods instance has been assigned a different GUID in its constructor. Using the “GetId” method that returns generated ID, client application could check to which server class instance it is talking to.

The source code for this demo is available at Code Central (http://cc.embarcadero.com/item/28199) and the video in two parts on YouTube (http://www.youtube.com/watch?v=VI_o6bwIfkM and http://www.youtube.com/watch?v=XcLOm-v7Ing).

You can find more information about "Delphi Labs" at my blog (http://blogs.embarcadero.com/pawelglowacki).

Server Response from: ETNASC01