10.07.2015 Views

The New DataSnap in Delphi 2009 - Embarcadero

The New DataSnap in Delphi 2009 - Embarcadero

The New DataSnap in Delphi 2009 - Embarcadero

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

Tech Notes<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>Build<strong>in</strong>g Multi-Tier Applications without Us<strong>in</strong>g a COM InterfaceMarco CantùDecember 2008Corporate Headquarters EMEA Headquarters Asia-Pacific Headquarters100 California Street, 12th FloorSan Francisco, California 94111York House18 York RoadMaidenhead, BerkshireSL6 1SF, United K<strong>in</strong>gdomL7. 313 La Trobe StreetMelbourne VIC 3000Australia


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>INTRODUCTIONFor a long time <strong>Delphi</strong> has <strong>in</strong>cluded a technology for build<strong>in</strong>g multi-tier database applications.Formerly known as MIDAS and later as <strong>DataSnap</strong>, <strong>Delphi</strong>'s multi-tier technology was based onCOM, even if the remote connectivity could be provided by sockets and HTTP, <strong>in</strong>stead ofDCOM. For some time, it even supported CORBA--a slightly modified version that providedSOAP connectivity.<strong>Delphi</strong> <strong>2009</strong> still <strong>in</strong>cludes the classic <strong>DataSnap</strong>, but provides a new remot<strong>in</strong>g and multi-tiertechnology as well. It is partially based on the dbExpress architecture. This new technology isstill called <strong>DataSnap</strong>, but to avoid confusion is generally referenced as “<strong>DataSnap</strong> <strong>2009</strong>”.BUILDING A DATASNAP <strong>2009</strong> DEMOBefore I get <strong>in</strong>to too many details, let me start with a simple three-tier database-oriented demo.This will help clarify a few po<strong>in</strong>ts and also cover differences from the previous version.BUILDING A SERVER<strong>The</strong> first step is build<strong>in</strong>g a <strong>DataSnap</strong> <strong>2009</strong> server application. This can be a standard VCLapplication, to which you add a server module (found <strong>in</strong> the <strong>Delphi</strong> Files page of the <strong>New</strong> Itemsdialog box and not <strong>in</strong> the Multitier page):Figure 1 Creat<strong>in</strong>g a Server Module <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong><strong>Embarcadero</strong> Technologies - 1 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>To the server module (but we could also have used a standard data module) you generally addthe dbExpress components to connect to the database server, plus a dataset provider toexpose the given datasets:object IBCONNECTION: TSQLConnectionConnectionName = 'IBCONNECTION'DriverName = 'Interbase'Log<strong>in</strong>Prompt = FalseParams.Str<strong>in</strong>gs = ('DriverName=Interbase''Database=C:\Program Files\...\Data\Employee.GDB')endobject EMPLOYEE: TSQLDataSetCommandText = 'EMPLOYEE'CommandType = ctTableSQLConnection = IBCONNECTIONendobject DataSetProviderEmployee: TDataSetProviderDataSet = EMPLOYEEendThis server module is built <strong>in</strong> a very similar way as it has been <strong>in</strong> the past. What is new is theneed to <strong>in</strong>clude three new components that provide configuration and connectivity <strong>in</strong> place ofthe COM support (which is totally gone). <strong>The</strong> three components are:• DSServer, the ma<strong>in</strong> server configuration component, which is needed to wire all the other<strong>DataSnap</strong> <strong>2009</strong> components together.• DSServerClass, a component needed for each class you want to expose. This component isnot the class you make available, but acts as a class factory to create objects of the class youwant to call from a remote client. In other words, the DSServerClass component will refer tothe class that has the public <strong>in</strong>terface.• DSTCPServerTransport, a component that def<strong>in</strong>es the transport protocol to be used (this isthe only protocol directly available <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>) and its configuration, such as whichTCP/IP port to use.In the demo these components are <strong>in</strong> the ma<strong>in</strong> form of the server, configured as follows:object DSServer1: TDSServerAutoStart = TrueHideDSAdm<strong>in</strong> = FalseOnConnect = DSServer1ConnectOnDisconnect = DSServer1Disconnectendobject DSTCPServerTransport1: TDSTCPServerTransportPoolSize = 0Server = DSServer1BufferKBSize = 32endobject DSServerClass1: TDSServerClassOnGetClass = DSServerClass1GetClassServer = DSServer1LifeCycle = 'Session'end<strong>Embarcadero</strong> Technologies - 2 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>We'll get to some of the details of these properties later on. <strong>The</strong> reason you don't see the valueof the TCP/IP port <strong>in</strong> the list<strong>in</strong>g above is that I've not modified the default value of 211.<strong>The</strong> only <strong>Delphi</strong> code you need to write is the “class factory” code that you need to connect theDSServerClass1 component to the server module expos<strong>in</strong>g the providers:procedure TFormFirst3Tier<strong>2009</strong>Server.DSServerClass1GetClass(DSServerClass: TDSServerClass;var PersistentClass: TPersistentClass);beg<strong>in</strong>PersistentClass := TDSFirst3TierServerModule;end;This is all you need for the server. I added a logg<strong>in</strong>g statement to the method above, as well asto the event handlers of the OnConnect and OnDisconnect events of the DSServercomponent.Aga<strong>in</strong>, there is no need to register it <strong>in</strong> any way. Simply run it, maybe us<strong>in</strong>g the Run | RunWithout Debugg<strong>in</strong>g command <strong>in</strong> the <strong>Delphi</strong> IDE, so you can build the client and connect it tothe server even at design time.THE FIRST CLIENTNow that we have a server available, we can move on and build the first client. In the <strong>DataSnap</strong><strong>2009</strong> client application, we need to use an SQLConnection component associated with the new<strong>DataSnap</strong> dbExpress driver and configured with the proper TCP/IP port.Next we need a DSProviderConnection component, used to refer to the server class, with theServerClassName property. This is not the <strong>in</strong>termediary class factory <strong>in</strong> the server(DSServerClass1), but the actual target of the class factory. In my example, it is theTDSFirst3TierServerModule class.As with a traditional <strong>DataSnap</strong> application, the ClientDataSet component can use the providerto fetch (and update) the remote dataset. First, you have to assign the RemoteServerproperty of the ClientDataSet, pick<strong>in</strong>g the DSProviderConnection1 component from thedrop-down list. Next, you can select the DataSetProviderEmployee provider from thedrop down of the ProviderName property, populated with all exported DataSetProvidercomponents of the remote data module.This is a summary of the properties of these components, <strong>in</strong>clud<strong>in</strong>g a DataSource used todisplay the database table <strong>in</strong> a DBGrid:object SQLConnection1: TSQLConnectionDriverName = 'Datasnap'endobject DSProviderConnection1: TDSProviderConnectionServerClassName = 'TDSFirst3TierServerModule'SQLConnection = SQLConnection1endobject ClientDataSet1: TClientDataSetProviderName = 'DataSetProviderEmployee'RemoteServer = DSProviderConnection1<strong>Embarcadero</strong> Technologies - 3 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>endobject DataSource1: TDataSourceDataSet = ClientDataSet1endThat's all it takes for an <strong>in</strong>troductory demo. Now if you run the server first and the client next,you can press the Open button of the client and see the database data. Also notice the logproduced by the server, shown <strong>in</strong> Figure 2:Figure 2 View Database Data as Well as Server LogsFROM DATASNAP TO DATASNAP <strong>2009</strong>Compared to the traditional <strong>DataSnap</strong> application, there are a few significant differences, morerelated to the architecture and deployment than the actual code you have to write:• <strong>The</strong>re is no COM <strong>in</strong>volved for the development of the server. Even if a client could alreadyuse sockets <strong>in</strong> the past, a socket-to-Com mapp<strong>in</strong>g service was required on the server. Nowthe client and server applications communicate directly over TCP/IP.• As a side effect, you don't have to register the server, nor run any helper service on it. All theserver has to provide to the client is an open TCP/IP port the client can reach• You must manually run the application on the server, or create a service for it. In the past theCOM support implied the server application would be started as needed.• <strong>The</strong> server implementation is slightly more complicated <strong>in</strong> terms of components, but there isvery little code beh<strong>in</strong>d the scenes, as for the COM counterpart.<strong>Embarcadero</strong> Technologies - 4 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>• <strong>The</strong> client implementation is almost identical, as we need a standard SQLConnectioncomponent, <strong>in</strong> place of a specific connection object.• On the server side, the TDSServerModule class <strong>in</strong>herits from TDataModule, <strong>in</strong>clud<strong>in</strong>gthe IAppServer <strong>in</strong>terface (the same <strong>in</strong>terface used <strong>in</strong> the past by a COM-basedTRemoteDataModule) and enabl<strong>in</strong>g the $MethodInfo compiler directive.• As the client-side dbExpress driver is a pure 100% <strong>Delphi</strong> driver, you don't need do deployany DLL on the client computer, even if you are us<strong>in</strong>g dbExpress for the connectivity.Pay a lot of attention when clos<strong>in</strong>g the server application. Unlike <strong>in</strong> the COM architecture, whichwarns you about pend<strong>in</strong>g connections, a <strong>DataSnap</strong> <strong>2009</strong> server will seem to close, but won'tuntil there are no rema<strong>in</strong><strong>in</strong>g connections to it. However, even after the connections have beenclosed it will rema<strong>in</strong> runn<strong>in</strong>g <strong>in</strong> memory, even if the ma<strong>in</strong> form is gone. You'll need to use TaskManager (or Process Explorer) to term<strong>in</strong>ate the server. You might th<strong>in</strong>k that clos<strong>in</strong>g all exist<strong>in</strong>gclient applications will be enough, but it is not: <strong>The</strong> <strong>Delphi</strong> IDE, <strong>in</strong> fact, can open a connectionto the server even automatically, for brows<strong>in</strong>g its exposed classes and methods. Be sure toclose any SQLConnection to the server before stopp<strong>in</strong>g it.ADDING SERVER METHODSAs <strong>in</strong> the past, you can write methods <strong>in</strong> the server that can be called by the client. <strong>The</strong>se werebased on COM, so you had to add <strong>in</strong>terfaces to the type library and implement them <strong>in</strong> theserver objects, and call the methods us<strong>in</strong>g COM dispatch <strong>in</strong>terfaces on the client. In <strong>DataSnap</strong><strong>2009</strong> the remote methods calls, or server method calls, are based on <strong>Delphi</strong>'s RTTI. Notice,however, that parameters pass<strong>in</strong>g is based on dbExpress parameter types, and not on <strong>Delphi</strong>language types.You can have multiple server side classes that expose methods, but to cont<strong>in</strong>ue with the simpleproject I've already built, I added an extra method to the server module class (<strong>in</strong> the serverapplication), us<strong>in</strong>g the follow<strong>in</strong>g code:typeTDSFirst3TierServerModule = class(TDSServerModule)IBCONNECTION: TSQLConnection;EMPLOYEE: TSQLDataSet;DataSetProviderEmployee: TDataSetProvider;private{ Private declarations }publicfunction GetHello: str<strong>in</strong>g;end;function TDSFirst3TierServerModule.GetHello: str<strong>in</strong>g;beg<strong>in</strong>Result := 'Hello from TDSFirst3TierServerModule at '+ TimeToStr (Now);end;To enable remote <strong>in</strong>vocation you have to connect the class for which you want to exposemethods to a DSServerClass factory. (In this case, we've already done so <strong>in</strong> the databaseportion of the demo). <strong>The</strong> second requirement is to use a class that is compiled with the$MethodInfo directive turned on, but this already takes place <strong>in</strong> the declaration of the base<strong>Embarcadero</strong> Technologies - 5 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>TDSServerModule class. This means that, <strong>in</strong> practice, all we have to do is to add a publicmethod to the server module, and everyth<strong>in</strong>g else will work.How do we call this server method from the client application? <strong>The</strong>re are basically twoalternatives. One is to use the new SqlServerMethod component and call the server method asif it was a stored procedure. <strong>The</strong> second is to generate a proxy class <strong>in</strong> the client applicationand use this proxy class to make the call.In the follow<strong>in</strong>g client demo I've implemented both approaches. For the first, I've added anSqlServerMethod component to the form of the client, tied it to the connection, picked a valuefor the ServerMethodName property <strong>in</strong> the Object Inspector (among the many available, asthe standard IAppServer <strong>in</strong>terface methods are listed as well), and checked the value of theParams property. This is a copy of the component sett<strong>in</strong>gs (which actually <strong>in</strong>clude the result ofa sample call performed when check<strong>in</strong>g the parameters):object SqlServerMethod1: TSqlServerMethodGetMetadata = FalseParams = SQLConnection = SQLConnection1ServerMethodName = 'TDSFirst3TierServerModule.GetHello'end<strong>The</strong> native str<strong>in</strong>g type is mapped to a str<strong>in</strong>g parameter of 2,000 characters. After configur<strong>in</strong>g theSqlServerMethod component, the program can call it us<strong>in</strong>g the <strong>in</strong>put parameters (none <strong>in</strong> thiscase) and the output parameters (the result) as <strong>in</strong> a stored procedure or query call:procedure TFormFirst3Tier<strong>2009</strong>Client.btnHelloClick(Sender: TObject);beg<strong>in</strong>SqlServerMethod1.ExecuteMethod;ShowMessage (SqlServerMethod1.Params[0].Value);end;To make it easier to write the call<strong>in</strong>g code we can use the second approach I mentioned earlier,creat<strong>in</strong>g a local proxy class <strong>in</strong> the client application. To accomplish this, we can ask the <strong>Delphi</strong>IDE to parse the <strong>in</strong>terface of the server class and create local proxy class for it, by click<strong>in</strong>g on theSQLConnection component and select<strong>in</strong>g the command Generate Datasnap client classes. Inthe case of this example, <strong>Delphi</strong> will generate a unit with the follow<strong>in</strong>g class (from which I'veomitted the code of the constructors and the destructor):typeTDSFirst3TierServerModuleClient = classprivateFDBXConnection: TDBXConnection;<strong>Embarcadero</strong> Technologies - 6 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>FInstanceOwner: Boolean;FGetHelloCommand: TDBXCommand;publicconstructor Create(ADBXConnection: TDBXConnection); overload;constructor Create(ADBXConnection: TDBXConnection;AInstanceOwner: Boolean); overload;destructor Destroy; override;function GetHello: str<strong>in</strong>g;end;function TDSFirst3TierServerModuleClient.GetHello: str<strong>in</strong>g;beg<strong>in</strong>if FGetHelloCommand = nil thenbeg<strong>in</strong>FGetHelloCommand := FDBXConnection.CreateCommand;FGetHelloCommand.CommandType :=TDBXCommandTypes.DSServerMethod;FGetHelloCommand.Text :='TDSFirst3TierServerModule.GetHello';FGetHelloCommand.Prepare;end;FGetHelloCommand.ExecuteUpdate;Result := FGetHelloCommand.Parameters[0].Value.GetWideStr<strong>in</strong>g;end;<strong>The</strong> generated code doesn't use the high level SqlServerMethod component, but rather callsdirectly <strong>in</strong>to the low-level dbExpress implementation objects, like the TDBXCommand class.Hav<strong>in</strong>g this proxy class available, the client program can now call the server method <strong>in</strong> a morelanguage-friendly way, although we do need to create an <strong>in</strong>stance of the proxy class (or createone and keep it around). This code does exactly the same as the previous code based on theSqlServerMethod component:procedure TFormFirst3Tier<strong>2009</strong>Client.btnHelloClick(Sender: TObject);beg<strong>in</strong>with TDSFirst3TierServerModuleClient.Create(SQLConnection1.DBXConnection) dotryShowMessage (GetHello);f<strong>in</strong>allyFree;end;end;If the code is actually longer than the previous version, this is because the method we arecall<strong>in</strong>g has no parameters, thus mak<strong>in</strong>g the language b<strong>in</strong>d<strong>in</strong>g code less relevant. Still, hav<strong>in</strong>g aready-to-use proxy object, we could have written:ShowMessage (ServerProxyObject.GetHello);<strong>Embarcadero</strong> Technologies - 7 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>SESSIONS AND THREADING WITH A NON-DATABASE DATASNAP SERVERIf us<strong>in</strong>g the IAppServer <strong>in</strong>terface directly is go<strong>in</strong>g to be the most common way for us<strong>in</strong>g<strong>DataSnap</strong> <strong>2009</strong>, it is possible to use this multi-tier technology for remote method <strong>in</strong>vocationoutside of the database context. You can also use the same technology to access database dataor perform database operations without us<strong>in</strong>g the IAppServer <strong>in</strong>terface, which is f<strong>in</strong>e if allyou want to do is read data from the server. If you want to let the client application modify thedata and post it back to the server, us<strong>in</strong>g custom methods could become tedious compared tothe ready-to-use IAppServer <strong>in</strong>terface, implemented by the ClientDataSet and theDataSetProvider components.In any case, <strong>in</strong> this second example, I want to create a m<strong>in</strong>imal server expos<strong>in</strong>g a couple ofsimple classes. In the follow<strong>in</strong>g sections I'll use this simple server to explore a couple of relevantissues, like server memory management and server (and client) thread<strong>in</strong>g.<strong>The</strong> first server class (with two methods) I want to publish <strong>in</strong> the DsnapMethodServer project isthe follow<strong>in</strong>g:{$MethodInfo ON}typeTSimpleServerClass = class(TPersistent)publicfunction Echo (const Text: str<strong>in</strong>g): str<strong>in</strong>g;function SlowPrime (MaxValue: Integer): Integer;end;{$MethodInfo OFF}<strong>The</strong> code of the first method simply echoes the <strong>in</strong>put, repeat<strong>in</strong>g its last part, while the secondmethod performs the most classic slow computation. This is the code of the two methods:function TSimpleServerClass.Echo(const Text: str<strong>in</strong>g): str<strong>in</strong>g;beg<strong>in</strong>Result := Text + '...' +Copy (Text, 2, max<strong>in</strong>t) + '...' +Copy (Text, Length (Text) - 1, 2);end;function TSimpleServerClass.SlowPrime(MaxValue: Integer): Integer;varI: Integer;beg<strong>in</strong>// counts the prime numbers below the given valueResult := 0;for I := 1 to MaxValue dobeg<strong>in</strong>if IsPrime (I) thenInc (Result);end;<strong>Embarcadero</strong> Technologies - 8 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>end;I've omitted the extra statements used to log the server operations from the code snippetabove.<strong>The</strong> server application has only one unit, which def<strong>in</strong>es the ma<strong>in</strong> form and two server sideclasses. <strong>The</strong> form has the usual <strong>DataSnap</strong> server components, a DSServer and aDSTCPServerTransport, plus two DSServerClass component, one for each of the classes I wantto expose. After compil<strong>in</strong>g the server and start<strong>in</strong>g it, I've let <strong>Delphi</strong> create a client proxy us<strong>in</strong>gthe SQLConnection component of a new client application. This is the client proxy class:typeTSimpleServerClassClient = classprivateFDBXConnection: TDBXConnection;FInstanceOwner: Boolean;FEchoCommand: TDBXCommand;FSlowPrimeCommand: TDBXCommand;publicconstructor Create(ADBXConnection: TDBXConnection); overload;constructor Create(ADBXConnection: TDBXConnection;AInstanceOwner: Boolean); overload;destructor Destroy; override;function Echo(Text: str<strong>in</strong>g): str<strong>in</strong>g;function SlowPrime(MaxValue: Integer): Integer;end;In the client program, the OnClick event of the button calls the Echo server method, aftercreat<strong>in</strong>g an <strong>in</strong>stance of the proxy, if needed:procedure TFormDsnapMethodsClient.btnEchoClick(Sender: TObject);beg<strong>in</strong>if not Assigned (SimpleServer) thenSimpleServer := TSimpleServerClassClient.Create (SQLConnection1.DBXConnection);Edit1.Text := SimpleServer.Echo(Edit1.Text);end;In the example, press<strong>in</strong>g this button the sample text “Marco” is transformed by the server call<strong>in</strong>to “Marco...arco...co”. This is a complete example of how you can create a totally customserver, with no database access <strong>in</strong>volved and no use of the IAppServer <strong>in</strong>terface. This is notthe only method <strong>in</strong>vocation technique available <strong>in</strong> <strong>Delphi</strong>, as you can use SOAP, socket-basedapplications, or third-party tools... but hav<strong>in</strong>g this extra feature on top of the remote databaseaccess capability is certa<strong>in</strong>ly a plus.One of the reasons I'm focus<strong>in</strong>g on this example is it helps clarify some relevant features of<strong>DataSnap</strong> <strong>2009</strong>. One of them is how server side objects relate to client proxies or server method<strong>in</strong>vocation. This is better demonstrated by a server object that keeps track of its own state, likethe follow<strong>in</strong>g second server class of the demo project:<strong>Embarcadero</strong> Technologies - 9 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>{$MethodInfo ON}typeTStorageServerClass = class(TPersistent)privateFValue: Integer;publicprocedure SetValue(const Value: Integer);function GetValue: Integer;function ToStr<strong>in</strong>g: str<strong>in</strong>g; override;publishedproperty Value: Integer read GetValue write SetValue;end;{$MethodInfo OFF}While the getter and setter methods simply read and write the local field, the ToStr<strong>in</strong>gfunction returns both the value and an object identifier based on its hash code:function TStorageServerClass.ToStr<strong>in</strong>g: str<strong>in</strong>g;beg<strong>in</strong>Result := 'Value: ' + IntToStr (Value) +' - Object: ' + IntToHex (GetHashCode, 4);end;I'll use this method to figure out how the life cycle of server objects work. In this class theproperty def<strong>in</strong>ition only makes sense for the server as it is not exposed to the client. <strong>The</strong><strong>in</strong>terface of the correspond<strong>in</strong>g proxy becomes (after remov<strong>in</strong>g private fields, standardconstructors and destructor):typeTStorageServerClassClient = classpublicprocedure SetValue(Value: Integer);function GetValue: Integer;function ToStr<strong>in</strong>g: str<strong>in</strong>g;Notice that compil<strong>in</strong>g this class produces the follow<strong>in</strong>g warn<strong>in</strong>g, unless you manually mark themethod as override:Method 'ToStr<strong>in</strong>g' hides virtual method of base type 'TObject'<strong>The</strong> goal of this example is to figure out what happens when multiple client applications use thesame server. <strong>The</strong> behavior of a <strong>DataSnap</strong> <strong>2009</strong> server <strong>in</strong> such a case depends on the value of theLifeCycle str<strong>in</strong>g property of the DSServerClass component be<strong>in</strong>g used.<strong>Embarcadero</strong> Technologies - 10 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>SERVER OBJECTS LIFE CYCLE<strong>The</strong> life cycle of <strong>DataSnap</strong> <strong>2009</strong> server objects depends on the correspond<strong>in</strong>g sett<strong>in</strong>g of therelated DSServerClass component. <strong>The</strong> LifeCycle property of this component can assumethe follow<strong>in</strong>g three str<strong>in</strong>g values (which are read from the DSServerClass components when theDSServer object is opened, ignor<strong>in</strong>g any change at runtime):• Session <strong>in</strong>dicates that the server will create a different object for each client socketconnection, that is, a server object for each client. <strong>The</strong> server objects are released when theconnection is closed. Multiple clients will have <strong>in</strong>dependent status and separate databaseaccess <strong>in</strong> case the server object is a data module, maybe with its own database connectioncomponent. This is the default sett<strong>in</strong>g.• Invocation <strong>in</strong>dicates that a new server object is created (and destroyed) every time theserver method is <strong>in</strong>voked. This is a classic stateless behavior, mak<strong>in</strong>g the server extremelyscalable, but also subject to fetch<strong>in</strong>g the same data over and over.• Server <strong>in</strong>dicates a shared server object, a s<strong>in</strong>gleton. Each client will use the same serverobject <strong>in</strong>stance, the same data, potentially caus<strong>in</strong>g synchronization problems (as differentclient <strong>in</strong>vocations are performed by different server threads). Access to shared serverobjects must be protected by synchronization techniques (for example us<strong>in</strong>g the newTMonitor record).Besides us<strong>in</strong>g these default sett<strong>in</strong>gs, you can customize the creation and destruction of serverside objects us<strong>in</strong>g the OnCreateInstance and OnDestroyInstance events of theDSServerClass component. This can be used to implement server-side object pool<strong>in</strong>g.A CLIENT STARTING THE SERVER AND OPENING MULTIPLECONNECTIONSAs a practical example, the DsnapMethods project lets you create multiple client connectionsfrom a s<strong>in</strong>gle <strong>in</strong>stance of a client application (us<strong>in</strong>g multiple <strong>in</strong>stances will yield the same result),You can create multiple <strong>in</strong>stances of the form that has the SQLConnection component andstore a local <strong>in</strong>stance of the client proxy that is created the first time it is used. Not only can theclient create multiple client connections, but it can also start the server program with a given lifecycle sett<strong>in</strong>g. This is easy to achieve because the client and the server application are on thesame computer.To accomplish this I've added to the unit of the ma<strong>in</strong> form of the server a global variable, usedto determ<strong>in</strong>e the DSServerClass LifeCycle property:varParamLifeCycle: str<strong>in</strong>g;procedure TFormDsnapMethodsServer.DSServerClass2GetClass(DSServerClass: TDSServerClass;var PersistentClass: TPersistentClass);beg<strong>in</strong>DSServerClass2.LifeCycle := ParamLifeCycle;Log ('LifeCycle: ' + DSServerClass2.LifeCycle);PersistentClass := TStorageServerClass;end;<strong>Embarcadero</strong> Technologies - 11 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong><strong>The</strong> value of the ParamLifeCycle global variable is <strong>in</strong>itialized us<strong>in</strong>g the command l<strong>in</strong>eparameters of the server application, which has the follow<strong>in</strong>g code at the beg<strong>in</strong>n<strong>in</strong>g of itsproject file source code:beg<strong>in</strong>if ParamCount > 0 thenParamLifeCycle := ParamStr(1);Application.Initialize;With this code available on the server, the ma<strong>in</strong> form of the client application (which has noconnection, as the connection is configured <strong>in</strong> the secondary forms) has a RadioGroup with thefollow<strong>in</strong>g values:object rgLifeCycle: TRadioGroupItemIndex = 0Items.Str<strong>in</strong>gs = ('Session''Invocation''Server')endWhen click<strong>in</strong>g on a button, the client program reads the current value and passes it asparameter to the server (notice you cannot run the server twice, as you cannot have the samelisten<strong>in</strong>g socket at the same port opened by two applications at the same time on a computer):procedure TFormDsmcMa<strong>in</strong>.btnStartServerClick(Sender: TObject);varaStr: AnsiStr<strong>in</strong>g;beg<strong>in</strong>Log (rgLifeCycle.Items[rgLifeCycle.ItemIndex]);aStr := 'DsnapMethodsServer.exe ' +rgLifeCycle.Items[rgLifeCycle.ItemIndex];W<strong>in</strong>Exec (PAnsiChar (aStr), CmdShow);end;Now the ma<strong>in</strong> form of the client application also has a button used to create <strong>in</strong>stances of thesecondary form, which are destroyed when they are closed (<strong>in</strong> their OnClose event handler),clos<strong>in</strong>g the specific connection to the server. Another button is used to log the status of thecurrent client forms:procedure TFormDsmcMa<strong>in</strong>.btnUpdateStatusClick(Sender: TObject);varI: Integer;beg<strong>in</strong>for I := 0 to Screen.FormCount - 1 doif Screen.Forms[I].ClassType = TFormDsmcClient thenLog (IntToStr (I) + ': ' +Screen.Forms[I].ToStr<strong>in</strong>g);end;<strong>Embarcadero</strong> Technologies - 12 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>When call<strong>in</strong>g ToStr<strong>in</strong>g for one of the secondary forms, this returns the status of theconnected server object, call<strong>in</strong>g its public ToStr<strong>in</strong>g method:function TFormDsmcClient.ToStr<strong>in</strong>g: str<strong>in</strong>g;beg<strong>in</strong>InitStorageServer;Result := StorageServer.ToStr<strong>in</strong>g;end;As a first execution example, I've created the server with the default Session life cycle, openedtwo client forms, set the values to 3 and 4, and asked for the overall status, with this result:Session1: Value: 3 - Object: 1C384002: Value: 4 - Object: 1C384E0In a second execution, I've gone for the Invocation life cycle, and ask<strong>in</strong>g for the overall statustwice I saw the follow<strong>in</strong>g output:Invocation1: Value: 0 - Object: 1D185B02: Value: 0 - Object: 1D184901: Value: 0 - Object: 1D185C02: Value: 0 - Object: 1D185D0Notice that you are gett<strong>in</strong>g a new object for each execution and the objects status is alwayszero (and any sett<strong>in</strong>g will immediately be lost when the object is destroyed immediately aftereach <strong>in</strong>vocation). Needless to say, this makes sense only for stateless operations.F<strong>in</strong>ally, I've repeated the same steps (sett<strong>in</strong>g values to 3 and 4) with the Server life cycle sett<strong>in</strong>g,and this time every client form uses the same server object, with the last value set:Server1: Value: 4 - Object: 1E084902: Value: 4 - Object: 1E08490In other words, the practice shows... that the theory is correct! While explor<strong>in</strong>g life cycleconfiguration <strong>in</strong> the demo, we've also looked at an example of a client start<strong>in</strong>g the (local) serverit needs and of a client with multiple concurrent connections to the server.PORTING AN OLD DATASNAP DEMOHav<strong>in</strong>g explored some of the alternatives with us<strong>in</strong>g <strong>DataSnap</strong> <strong>2009</strong>, let me get back to themost classic usage scenario, which is a multi-tier database application. We’ve already seen thesteps for creat<strong>in</strong>g a brand new <strong>DataSnap</strong> database application. Now let’s focus on an equallyrelevant issue: port<strong>in</strong>g an exist<strong>in</strong>g <strong>DataSnap</strong> (or MIDAS) application to this new architecture.As a practical example, I've decided to port the Th<strong>in</strong>Plus application of Master<strong>in</strong>g <strong>Delphi</strong> 2005,which showcases a few capabilities of <strong>DataSnap</strong>, and lets me cover a more complete example,<strong>The</strong> example also focuses on what needs to be done to port a COM server <strong>in</strong>voked from a<strong>Embarcadero</strong> Technologies - 13 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>client us<strong>in</strong>g a socket to a pure socket-based architecture. <strong>The</strong> new example (with server andclient projects) is <strong>in</strong> the Th<strong>in</strong>Plus<strong>2009</strong> folder.Notice that port<strong>in</strong>g <strong>DataSnap</strong> applications to the new architecture is an <strong>in</strong>terest<strong>in</strong>g option, butnot a compulsory one. Traditional <strong>DataSnap</strong> servers and clients can still compile and workproperly <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>.(<strong>The</strong> program is described <strong>in</strong> detail <strong>in</strong> the book Master<strong>in</strong>g <strong>Delphi</strong> 2005, but also <strong>in</strong> previouseditions like Master<strong>in</strong>g <strong>Delphi</strong> 7. Here I'll provide only an overview of some of its features.Those books can certa<strong>in</strong>ly give you a broader picture of the orig<strong>in</strong>al features of <strong>DataSnap</strong> andpreviously MIDAS, which are mostly still available <strong>in</strong> the <strong>Delphi</strong> <strong>2009</strong> version.)PORTING THE SERVERFor port<strong>in</strong>g the server project, I followed these steps:• I removed the <strong>in</strong>itialization section of the remote data module unit, called AppsRDM. <strong>The</strong>code removed was the call to the constructor of the TComponentFactory class.• I also removed the UpdateRegistry class method of the TAppServerPlus class fromthe same remote data module unit.• At that po<strong>in</strong>t I could elim<strong>in</strong>ate from the uses clause of the remote data module the COMand ActiveX related units: ComServ, ComObj, VCLCom, and StdVcl.• Next I had to remove the reference to the custom IAppServerPlus <strong>in</strong>terface that wasused by the project to provide custom server methods (the <strong>in</strong>terface was def<strong>in</strong>ed <strong>in</strong> theproject type library).• I deleted the type library and RIDL file (just created when the project was opened <strong>in</strong> <strong>Delphi</strong><strong>2009</strong>) from the project and the disk. I also had to remove a uses statement referr<strong>in</strong>g to thetype library unit.• I moved the only server method (Log<strong>in</strong>) from the protected section to the public section ofthe remote data module class, remov<strong>in</strong>g from it the safecall modifier. As theTRemoteDataModule class is already compiled with $MethodInfo turned on, there isno need to add this declaration to the project unit.• F<strong>in</strong>ally, I added to the ma<strong>in</strong> form of the program the usual trio of components (server, serverclass, and server transport), wired them together, and returned the TAppServerPlus <strong>in</strong>the OnGetClass event handler of the server class component.That was all it took to upgrade an old <strong>DataSnap</strong> server to the <strong>Delphi</strong> <strong>2009</strong> version. It mightseem a lot, but it was actually quite fast. Now it was time to look <strong>in</strong>to the client application, onethat does a few custom operations.UPGRADING THE CLIENTPort<strong>in</strong>g the client application to <strong>DataSnap</strong> <strong>2009</strong> is generally easier than port<strong>in</strong>g the server. <strong>The</strong>core step is to remove the connection components (my demo had three, as it let usersexperiment with the various connectivity options) and replace it with an SQLConnection and aDSProviderConnection, and make the ClientDataSet component refer to this new remoteconnection component.<strong>Embarcadero</strong> Technologies - 14 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong><strong>The</strong> only specific code I had to change was the call to the Log<strong>in</strong> server method. This tookplace <strong>in</strong> the OnAfterConnection of the connection component, and I've now moved it tothe correspond<strong>in</strong>g event of the SQLConnection component:procedure TClientForm.SQLConnection1AfterConnect(Sender: TObject);beg<strong>in</strong>// was: ConnectionBroker1.AppServer.// Log<strong>in</strong> (Edit2.Text, Edit3.Text);SqlServerMethod1.ParamByName('Name').AsStr<strong>in</strong>g :=Edit2.Text;SqlServerMethod1.ParamByName('Password').AsStr<strong>in</strong>g :=Edit3.Text;SqlServerMethod1.ExecuteMethod;end;What this call does is to pass client log<strong>in</strong> <strong>in</strong>formation to the server. <strong>The</strong> server validates the<strong>in</strong>formation and, only if it succeeds, it will let the provider expose its data. <strong>The</strong> password checkis trivial, but the approach could be <strong>in</strong>terest<strong>in</strong>g. This is the Log<strong>in</strong> method on the server:procedure TAppServerPlus.Log<strong>in</strong>(const Name, Password: WideStr<strong>in</strong>g);beg<strong>in</strong>if Password Name thenraise Exception.Create ('Wrong name/password comb<strong>in</strong>ation received');ProviderDepartments.Exported := True;ServerForm.Add ('Log<strong>in</strong>:' + Name + '/' + Password);end;Notice that <strong>in</strong> case the server returns an exception this will be clearly displayed (<strong>in</strong>dicat<strong>in</strong>gwhere it comes from, Remote error) on the client side, shown <strong>in</strong> Figure 3:Figure 3 Clear Error Messag<strong>in</strong>g for Remote Errors<strong>Embarcadero</strong> Technologies - 15 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>ADVANCED FEATURES OF THINPLUS<strong>2009</strong>I upgraded the Th<strong>in</strong>Plus client and server applications to <strong>DataSnap</strong> <strong>2009</strong> follow<strong>in</strong>g the stepsmentioned earlier, even if these are some rather complex <strong>DataSnap</strong> programs, with severalcustomizations. <strong>The</strong>se <strong>in</strong>clude fetch<strong>in</strong>g data packets manually, us<strong>in</strong>g a master/details structure,execut<strong>in</strong>g a parametric query, transferr<strong>in</strong>g extra data along with the data packets, and thecustom remote log<strong>in</strong> I've just covered.It is worth hav<strong>in</strong>g a look at these features, even if briefly, as they should help those of you thathave never used <strong>DataSnap</strong> (or not a lot) to appreciate its power. Those who have used it alreadywill figure out how easily the code can be ported to the new architecture. <strong>The</strong> server applicationdef<strong>in</strong>ed a master/details structure, based on the follow<strong>in</strong>g sett<strong>in</strong>gs of the (respectively) provider,the master data set, the data source used to refer to it, and the details dataset that refers to thedata source:object ProviderDepartments: TDataSetProviderDataSet = SQLDepartmentsendobject SQLDepartments: TSQLDataSetCommandText = 'select * from DEPARTMENT'SQLConnection = SQLConnection1endobject DataSourceDept: TDataSourceDataSet = SQLDepartmentsendobject SQLEmployees: TSQLDataSetCommandText ='select * from EMPLOYEE where dept_no = :dept_no'DataSource = DataSourceDeptParams = SQLConnection = SQLConnection1endOn the client side, the program uses a first ClientDataSet connected with the provider and asecond ClientDataSet that refers to a special data set field of the first one:object cds: TClientDataSetFetchOnDemand = FalsePacketRecords = 5ProviderName = 'ProviderDepartments'RemoteServer = DSProviderConnection1object cdsDEPT_NO: TStr<strong>in</strong>gField...object cdsDEPARTMENT: TStr<strong>in</strong>gField......object cdsSQLEmployees: TDataSetFieldFieldName = 'SQLEmployees'endendobject cdsDet: TClientDataSet<strong>Embarcadero</strong> Technologies - 16 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong>DataSetField = cdsSQLEmployeesend<strong>The</strong> data of the two ClientDataSet components is displayed <strong>in</strong> two DBGrid controls. Notice howthe program fetches only 5 records (as <strong>in</strong>dicated <strong>in</strong> the PacketRecords property) <strong>in</strong> eachdata packet, and will stop fetch<strong>in</strong>g data after the first packet (as the FetchOnDemandproperty is False), even if the grid <strong>in</strong> not full. You can see this <strong>in</strong> the follow<strong>in</strong>g snapshot of theclient user <strong>in</strong>terface just after open<strong>in</strong>g the connection, shown <strong>in</strong> Figure 4:Figure 4 Client Interface of the Th<strong>in</strong>Plus ApplicationFollow<strong>in</strong>g data packets are fetched manually, as the user clicks the correspond<strong>in</strong>g button:procedure TClientForm.btnFetchClick(Sender: TObject);beg<strong>in</strong>btnFetch.Caption := IntToStr (cds.GetNextPacket);end;In the button caption, the program shows how many records it fetched <strong>in</strong> each packet. This willbe 5 while there are enough records, then the number or rema<strong>in</strong><strong>in</strong>g records, and f<strong>in</strong>ally zerowhen all the records have already been retrieved. At each fetch request the client DBGrid willshow more data, and its scrollbar will be updated accord<strong>in</strong>gly. You can also use thebntRecCount button to ask how many records have been retrieved so far.<strong>Embarcadero</strong> Technologies - 17 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong><strong>The</strong> client program has a second form, displayed by press<strong>in</strong>g the Query button, with anotherclient dataset. This ClientDataSet component is connected with a parametric query def<strong>in</strong>ed bythe server as:object SQLWithParams: TSQLDataSetCommandText ='select * from employee where job_code = :job_code'Params = SQLConnection = SQLConnection1end<strong>The</strong> client program has a list box, filled at design time with the department names, which isused to pass the proper parameter to the server. Notice that to write this code you have first toupdate the def<strong>in</strong>ition of the parameters, an operation you can do at design time by us<strong>in</strong>g thecorrespond<strong>in</strong>g component editor command for the ClientDataSet component. This is the callused on the client to execute the remote parametric query:procedure TFormQuery.btnParamClick(Sender: TObject);beg<strong>in</strong>cdsQuery.Close;cdsQuery.Params[0].AsStr<strong>in</strong>g := ComboBox1.Text;cdsQuery.Open;...On the server, when this query is executed the OnGetDataSetProperties event of theprovider adds extra <strong>in</strong>formation to the returned data packet:procedure TAppServerPlus.ProviderQueryGetDataSetProperties(Sender: TObject;DataSet: TDataSet; out Properties: OleVariant);beg<strong>in</strong>Properties := VarArrayCreate([0,1], varVariant);Properties[0] := VarArrayOf(['Time', Now, True]);Properties[1] := VarArrayOf(['Param', SQLWithParams.Params[0].AsStr<strong>in</strong>g, False]);end;Notice that the use of variant array parameters still works, even if the transport mechanism usedby <strong>DataSnap</strong> <strong>2009</strong> is now different. On the client side, the btnParamClick event handler hastwo more l<strong>in</strong>es of code to retrieve these extra properties from the data packet:Caption := 'Data sent at ' + TimeToStr (TDateTime (cdsQuery.GetOptionalParam('Time')));Label1.Caption := 'Param ' +cdsQuery.GetOptionalParam('Param');<strong>Embarcadero</strong> Technologies - 18 -


<strong>The</strong> <strong>New</strong> <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong><strong>The</strong>re are a few more features <strong>in</strong> <strong>DataSnap</strong> that have been moved over to the new version, butthis overview of the Th<strong>in</strong>Plus<strong>2009</strong> program (mostly unchanged from its orig<strong>in</strong>al version written <strong>in</strong><strong>Delphi</strong> 6) should be enough for my goals: Show you the power of <strong>DataSnap</strong> and how easy it isto migrate even a complex application to the <strong>Delphi</strong> <strong>2009</strong> socket-based (and COM-free) versionof <strong>DataSnap</strong>.CONCLUSIONIn this paper I’ve covered one the most significant updates <strong>in</strong> terms of the component library <strong>in</strong><strong>Delphi</strong> <strong>2009</strong>: the new <strong>DataSnap</strong> architecture for build<strong>in</strong>g multi-tier applications without hav<strong>in</strong>gto resort to COM. You can use <strong>DataSnap</strong> <strong>2009</strong> for database programm<strong>in</strong>g, but also to easily callany server-side method.Multitier applications based on sockets and with no need for COM registration either at theclient or the server-side simplify deployment and make it easier to work with firewalls. <strong>DataSnap</strong><strong>2009</strong> leverages the exist<strong>in</strong>g <strong>DataSnap</strong> architecture, a very powerful multi-tier architecture thatallows you deploy some of the bus<strong>in</strong>ess logic <strong>in</strong> the middle tier, open<strong>in</strong>g it up to a moremodern (and native) approach. <strong>The</strong> new <strong>DataSnap</strong> <strong>in</strong> <strong>Delphi</strong> <strong>2009</strong> offers foundations for futureextensions, like an HTTP transport protocol.F<strong>in</strong>ally, the recently shipp<strong>in</strong>g <strong>Delphi</strong> Prim (and new <strong>Delphi</strong> for .NET development environmentby CodeGear) has the ability to create client applications for <strong>DataSnap</strong> <strong>2009</strong>. As an example,you could create an ASP.NET project that connects to a database through a <strong>DataSnap</strong> <strong>2009</strong>server written <strong>in</strong> <strong>Delphi</strong> and compiled for W<strong>in</strong>32.It is likely that further extensions will let you use <strong>DataSnap</strong> for mov<strong>in</strong>g data and issu<strong>in</strong>g requests<strong>in</strong> other scenarios, but the current TCP/IP architecture is already a solid foundation that makes<strong>DataSnap</strong> <strong>2009</strong> a more flexible solution that its previous version and of many compet<strong>in</strong>gapproaches.ABOUT THE AUTHORThis paper has been written for <strong>Embarcadero</strong> Technologies by Marco Cantù, author of the bestsell<strong>in</strong>gseries Master<strong>in</strong>g <strong>Delphi</strong>. <strong>The</strong> content has been extracted from his latest book “<strong>Delphi</strong><strong>2009</strong> Handbook”, http://www.marcocantu.com/dh<strong>2009</strong>. You can read about Marco on his blog(http://blog.marcocantu.com) and reach him at his email address: marco.cantu@gmail.com.<strong>Embarcadero</strong> Technologies - 19 -


<strong>Embarcadero</strong> Technologies, Inc. is a lead<strong>in</strong>g provider of award-w<strong>in</strong>n<strong>in</strong>g tools for applicationdevelopers and database professionals so they can design systems right, build them faster andrun them better, regardless of their platform or programm<strong>in</strong>g language. N<strong>in</strong>ety of the Fortune100 and an active community of more than three million users worldwide rely on <strong>Embarcadero</strong>products to <strong>in</strong>crease productivity, reduce costs, simplify change management and complianceand accelerate <strong>in</strong>novation. <strong>The</strong> company’s flagship tools <strong>in</strong>clude: <strong>Embarcadero</strong> ® ChangeManager, CodeGear RAD Studio, DBArtisan ® , <strong>Delphi</strong> ® , ER/Studio ® , JBuilder ® and RapidSQL ® . Founded <strong>in</strong> 1993, <strong>Embarcadero</strong> is headquartered <strong>in</strong> San Francisco, with offices locatedaround the world. <strong>Embarcadero</strong> is onl<strong>in</strong>e at www.embarcadero.com..

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!