DataSnap 2009 Overview

By: Steven Shaughnessy

Abstract: This is an introduction to the new component based DataSnap technology included in Delphi 2009.

Delphi 2009 includes a new component based DataSnap server technology.  With some minor modifications, applications can host their existing TRemoteDataModules inside the new DataSnap 2009 server. The DataSnap 2009 server provides light weight remote method invocation support for ordinary Delphi classes as a replacement for COM based custom methods employed by earlier versions of DataSnap. The DataSnap 2009 server does not directly support COM.  If an application still needs a COM based solution, the older component set for DataSnap is also included in the product.

For new applications that do not need to use COM, the new TDSServerModule can be used instead of TRemoteDataModule. TDSServerModule indirectly inherits from TDataModule and includes the same IAppServer synchronization capability using providers that TRemoteDataModule provides. TDSServerModule is different from TRemotedataModule in that it does not include the COM support that TRemoteDataModule provides.

Object Pascal methods that are called from remote clients are called server methods. There are several ways that server methods can be invoked.  Server methods can be invoked from dbExpress or ADO.NET clients in the same way that stored procedures are called using these drivers.  Statically typed client side proxies classes can also be generated for server methods if needed.

A server connection is a feature of DataSnap 2009 that allows a client and server application to share a server side dbExpress database connection and transactional context.  Only the DataSnap dbExpress driver is needed on the client to share any dbExpress driver installed on the server.  The DataSnap driver is 100% Object Pascal making for a simple client side deployment.  An interesting application of server connections is to allow the client to perform select queries to read data, and to call server methods that use the same server connection to perform all insert, update and delete operations on the data.

The new DataSnap 2009 includes a component set implemented in Object Pascal.  We currently support win32 based server side applications.  On the client we currently support both win32 and .net applications.  Server methods and server connections can both be accessed through our dbExpress and ADO.NET connectivity solutions.

    Creating a DataSnap 2009 server

The first thing you will need is a server container. You can use a TDataModule or a TForm as your container. The container must include at least a TDSServer component and a transport component. A TDSTCPServerTransport transport component is included in the product. A TDSServerClass component is used to register a server class that contains providers or server methods that they need to be accessed by a client. Typically you will have one or more TDSServerClass components in the container. A separate TDSServerClass component is needed for each server class you want to expose to clients. Each transport and TDSServerClass component has a server property that is used to register them with a TDSServer component. When the TDSServer component start and stop methods are called all registered transports and server classes are notified so they can perform any necessary initialization when the server starts and deinitialization when the server stops. The transport component takes advantage of this notification to know when to start and stop listening for new connections and to initialize and shutdown its thread pool. The TDSServer component has an autostart property that causes the TDSServer component start method to be called when its TDataModule or TForm container is loaded.

Although we currently only provide one server side transport implementation, DataSnap 2009 can simultaneously support multiple transports. This will allow an HTTP and TCP/IP based transports to provide access to the same DataSnap 2009 server context.

    Creating Server methods.


Delphi developers will most likely want to add their server methods to a TDSServerModule class.  Delphi classes that contain server methods must be compiled with METHOD_INFO enabled because Delphi’s RTTI is needed for the dynamic invocation of server methods.  If TDSServerModule is used, METHOD_INFO does not have to be specified because TDSServerModule already has METHOD_INFO enabled. METHOD_INFO has been enabled for TRemoteDataModule as well. As mentioned earlier, TDSServerModule also supports the IAppServer interface, so the traditional IAppServer data synchronization protocol can also be used with TDSServerModules. All server methods must be made public.

Note that with DataSnap 2009, multiple TDSServerModules and TRemoteDataModules can exist in the same process. You do not need a separate server for each TDSServerModule or TRemoteDataModule.

Here is a simple example of a TDSServerModule with server methods using a variety of parameter types and parameter directions:

  TParametersServerModule1 = class(TDSServerModule)
    SQLConnection1: TSQLConnection;
    ReturnSqlQuery: TSQLQuery;
    OutSqlQuery: TSQLQuery;
    VarSqlQuery: TSQLQuery;
    procedure DSServerModuleCreate(Sender: TObject);
    procedure DSServerModuleDestroy(Sender: TObject);
  private
    { Private declarations }
    FDataSnapTestData: TDataSnapTestData;
    FReturnCommand: TDBXCommand;
    FOutCommand: TDBXCommand;
    FVarCommand: TDBXCommand;
  public
    { Public declarations }
    function DataSetMethod(InDataSet: TDataSet; out OutDataSet: TDataSet; 
      var VarDataSet: TDataSet): TDataSet;
    function ParamsMethod(InParams: TParams; out OutParams: TParams; 
      var VarParams: TParams): TParams;
    function StreamMethod(InStream: TStream; out OutStream: TStream; 
      var VarStream: TStream): TStream;
    function DBXReaderMethod(InDBXReader: TDBXReader; out OutDBXReader: TDBXReader; 
      var VarDBXReader: TDBXReader): TDBXReader;
    function VariantMethod(InVariant: OleVariant; out OutVariant: OleVariant; 
      var VarVariant: OleVariant): OleVariant;
    function StringMethod(InString: String;  out OutString: OleVariant; 
      var VarString: OleVariant): String;
    function Int64Method(InInt64: Int64; out OutInt64: Int64; 
      var VarInt64: Int64): Int64;
    procedure NoparametersMethod;
  end;

Server method parameters directions can be specified as input, input/output (var), output (out), and return.  Parameter types supported include the scalar values supported by dbExpress/ADO.NET drivers such as integer, string, date, time, floating point, etc.  Very large binary parameters can be passed as a TStream.  A TParams object can be used to pass an array of values or to return a single row result to the client.  Table shaped objects such as a TDataSet or a TDBXReader can be used as parameters and return values.  On the .net platform, a DataReader, DataView or DataTable can be used as parameters by the client.  Note that for table parameters the object type used on the client does not have to match the object type used in the signature of the server method.  This is because the messaging layer maps all table objects to a TDBXReader.  Custom TDBXReaders are fairly simple to implement.  It should not be too difficult to map a collection of object for an OR mapping technology into a TDBXReader inside a server method and return it to a client which reads it into a TDataSet.

Here is the implementation of a subset of the methods from the class interface section listed above:

function TParametersServerModule1.DataSetMethod(InDataSet: TDataSet;
  out OutDataSet: TDataSet; var VarDataSet: TDataSet): TDataSet;
begin
  // Validate the contents of the incoming DataSets
  // Incoming DataSets are automatically freed.
  //
  FDataSnapTestData.ValidateDataSet(InDataSet);
  FDataSnapTestData.ValidateDataSet(VarDataSet);

  // None of the outgoing TDataSets will be automatically freed because their
  // component owner is not nil.
  //
  OutSqlQuery.Open;
  OutSqlQuery.Refresh;
  OutDataSet := OutSqlQuery;

  VarSqlQuery.Open;
  VarSqlQuery.Refresh;
  VarDataSet := VarSqlQuery;

  ReturnSqlQuery.Open;
  ReturnSqlQuery.Refresh;
  Result := ReturnSqlQuery;
end;

function TParametersServerModule1.ParamsMethod(InParams: TParams;
  out OutParams: TParams; var VarParams: TParams): TParams;
begin
  // Validate the contents of the incoming TParams.
  //
  FDataSnapTestData.ValidateParams(InParams);
  FDataSnapTestData.ValidateParams(VarParams);

  // All of these outgoing return values will be automatically freed.
  //
  OutParams := FDataSnapTestData.CreateTestParams;
  VarParams := FDataSnapTestData.CreateTestParams;
  Result    := FDataSnapTestData.CreateTestParams;
end;


function TParametersServerModule1.StreamMethod(InStream: TStream;
  out OutStream: TStream; var VarStream: TStream): TStream;
begin
  // Validate the contents of the incoming Streams.
  //
  FDataSnapTestData.ValidateStream(InStream);
  FDataSnapTestData.ValidateStream(VarStream);

  // All of these outgoing values will be automatically freed.
  //
  OutStream := FDataSnapTestData.CreateTestStream;
  VarStream := FDataSnapTestData.CreateTestStream;
  Result    := FDataSnapTestData.CreateTestStream;

end;

    Exposing server methods to clients

Once you have implemented your server methods, they must be exposed by a DataSnap server.  This is easy.  Its best to create a separate “container TDataModule (or TForm) for your DataSnap server.  Create a new TDataModule TForm container and drop three components into it from the new “DataSnap Server” component category:  TDSServer, TDSTCPServerTransport, and TDSServerClass.  Set the TDSTCPServerTransport.Server property and the TDSServerClass.Server property to the TDSServer.  The TDSServerClass.OnGetClass event must be set to your server class like this:

procedure TForm8.DSServerClass2GetClass(DSServerClass: TDSServerClass;
  var PersistentClass: TPersistentClass);
begin
  PersistentClass := TParametersServerModule1;
end;

Now execute the application and the server will be started automatically.  There is a TDSServer.AutoStart property that defaults to true which causes the server to start when it is notified that the component is being loaded.

    Calling server methods using TSQLServerMethod component

From the client’s perspective, server methods look a lot like stored procedures when using the TSQLServerMethod component.  There are some notable exceptions though.  Server methods can send multiple table shaped objects from the server to the client and from the client to the server.  Here is a simple example of a client calling one of the server methods above:

procedure TForm13.TestComponentDataSet;
begin
  SqlServerMethod1.CommandText := 'TParametersServerModule1.DataSetMethod';

  SqlServerMethod1.Params[0].AsDataSet := FDataSnapTestData.CreateTestDataSet;
  // This has the same affect as the line above.  Ownership of the parameter instance is implicit.
  // Second boolean parameter indicates the TParam to free the TDataSets object
  // when it is no longer needed.
  //
  SqlServerMethod1.Params[2].SetDataSet(FDataSnapTestData.CreateTestDataSet, True);

  SqlServerMethod1.Open;

  // Return value is in the SQLServerMethod1 component.
  // outgoing instances will be automatically freed the next time the command
  // is executed, closed or freed.
  //
  FDataSnapTestData.ValidateDataSet(SqlServerMethod1);
  FDataSnapTestData.ValidateDataSet(SqlServerMethod1.Params[1].AsDataSet);
  FDataSnapTestData.ValidateDataSet(SqlServerMethod1.Params[2].AsDataSet);

  SqlServerMethod1.Close;
end;
 

    Calling Server methods using generated client classes

As discussed earlier, client proxy classes can be generated by using the DSProxyGen.exe command line tool or by using the right click menu off of the TSQLConnection component that is connected to a running DataSnap server. Currently this menu item is labeled “Generate DataSnap client classes”. This provides a statically typed access to server methods. Here is an example of how to call the “DataSetMethod” above using a generated client class:

procedure TForm13.TestClassDataSet;
var
  InDataSet: TDataSet;
  VarDataSet: TDataSet;
  OutDataSet: TDataSet;
  ReturnDataSet: TDataSet;
begin
  InDataset     := FDataSnapTestData.CreateTestDataSet;
  VarDataSet    := FDataSnapTestData.CreateTestDataSet;

  // By default, the command used inside the implementation of DataSetMethod
  // will maintain ownership of all parameters.  They will be freed when the
  // method is called again or when FParametersTest instance is freed.
  //
  ReturnDataSet := FParametersTest.DataSetMethod(InDataSet, OutDataSet, VarDataSet);

  FDataSnapTestData.ValidateDataSet(OutDataSet);
  FDataSnapTestData.ValidateDataSet(VarDataSet);
  FDataSnapTestData.ValidateDataSet(ReturnDataSet);
end;

    Accessing IAppServer providers from a client

This works the same as prior versions of IAppServer except that the new TDSProviderConnection component must be used instead of components like TDCOMConnection and TSocketConnection. TDSProviderConnection has a SQLConnection property. This allows multiple TDSProviderConnection components to share the same “physical” TSQLConnection component.

Note that multiple TDSServerModules or TRemoteDataModules can be included in a single server. By sharing a single TSQLConnection instance, multiple TDSProviderConnections can access there respective TDSServerModule or TRemoteDataModule.

    Hosting existing remote data modules

RemoteDataModules are configured similar to any other server class.  As with other server classes, you will need to use a TDSServerClass component and wire the OnGetClass event to return your RemoteDataModule class. Here are some notes from Dan Miser on how to migrate an existing TRemoteDataModule to a DataSnap 2009 server:

  1. Unregister the COM-based app server (e.g. MyAppServer /unregserver)
  2. Delete initialization section from the RDM unit
  3. Delete the tlb and ridl files from the project
  4. Remove the UpdateRegistry declaration and implementation from the RDM unit.
  5. Move any interface methods that you will want to call from the client (by default, these were added to the protected section of the RDM) to the public section.

NOTE: If you have a suite of COM-based server applications, you may want to take this opportunity to start moving all of them into one process. Since we are no longer reliant upon COM for application segregation, we can add the converted RDMs into one project and just add one TDSServerClass component for each RDM that we want to access.

    Using server connections

The DataSnap dbExpress connection can be used by a client application to access a server side dbExpress connection.  By default, a DataSnap dbExpress connection is only able to execute server methods on the DataSnap server it is connected to.  If the TDBXPropertyNames.ServerConnection property is set to a server method that returns an instance of TDBXConnection, then the client side DataSnap connection will also be able to execute sql commands against the returned TDBXConnection.   Here is an example of a server method that returns an instance of TDBXConnection:

function TDSServerModule1.GetConnection: TDBXConnection;
begin
  SQLConnection1.Open;
  Result := SQLConnection1.DBXConnection;
end;
 

A client side connection can establish associate itself with this connection by setting its ServerConnection connection property to:

ServerConnection=ServerMethodsDataModule.GetServerConnection 

Note that there is a built-in Server method called DSAdmin.GetConnection that can be used to establish server connections using the dbxconnections.ini on the server.  This server method has a string input parameter which is used to specify what connection to use.  This example shows how to set the server connection to an ibconnection item in the dbxconnections.ini file:

ServerConnection=DSAdmin.GetConnection("ibconnection")

Server Response from: ETNASC04