Stateless App Servers with MIDAS 3

By: John Kaster

Abstract: Dan Miser explains how MIDAS 3's stateless servers function

Stateless app servers with MIDAS 3

By Dan Miser of Distribucon

MIDAS 3 introduced the concept of a stateless app server. What is a stateless app server? Simply defined, it is one that does not remember anything between method calls. For example, setting several properties on the server and calling a method which acts on those properties is stateful because the app server needs to know the values of the properties that the client set prior to the current method call. One solution to this is to pass in all of the variables required for a method call as parameters. By doing things in this manner, you are giving the server everything it needs to know at the point when it needs the information.

The change from stateful app server to stateless app server is significant for several reasons:

  • The new IAppServer interface requires fewer round-trip network calls than the analagouscalls to IProvider. This results in an increase in speed for your application.
  • MIDAS app servers can now play in stateless environments like MTS with little or no work.
  • Stateless app servers can scale better than stateful app servers because the server does not need to keep resources tied up for state information.
  • Stateless app servers can use pooling, load-balancing and fail-over with ease.
With all of these advantages, you must be wondering what the catch is. Well, the catch is that you must write your code with statelessness in mind. The migration path from stateful to stateless for vanilla MIDAS applications is fairly painless. However, if you've written custom methods and code, you need to double-check that you are not inadvertently introducing state into your server.

One common place that state gets introduced is by using the ClientDataset.PacketRecords property to control how many records get brought from the server to the client at one time. The reason this introduces state is that the server cannot know how many records a certain client has retrieved. Borland provided solutions for this problem in Delphi's online help file and README.TXT file. Unfortunately,the solutions don't work.

Here is the code from the README.TXT file:

TDataModule1.ClientDataSet1BeforeGetRecords(
Sender: TObject; var OwnerData: OleVariant);
var
  CurRecord: TBookMark;
begin
  with Sender as TClientDataSet do
  begin
    if not Active then Exit; //added
    CurRecord := GetBookmark; 
      { save the current record }
    try
      Last; {locate the last record in the new packet }
      OwnerData := FieldValues['Key']; 
         { Send key value to the application server }
      GotoBookmark(CurRecord); 
         { return to current record }
    finally
      FreeBookmark(CurRecord);
    end;
  end;
end;

TRemoteDataModule1.Provider1BeforeGetRecords(
Sender: TObject; var OwnerData: OleVariant);
begin
  with Sender as TDataSetProvider do // was TProvider
      if not VarIsEmpty(OwnerData) then //added
        if DataSet.Locate('Key', OwnerData, []) then
        DataSet.Next; //added
end;
The main problem with this code is that the call to Last in the client will trigger a call to the app server to retrieve all of the records to the client. This clearly is in contradiction with your desired intent to fetch as few records as possible.

A couple of options to fix this would be to use a cloned cursor on the ClientDataset and call Last on the cloned cursor, or pass the key value around between client and server using the new BeforeGetRecords and AfterGetRecords events. I think the second approach is a bit cleaner and more generic than the first.

Here is the code to be implemented on the server Remote DataModule:

procedure TStatelessRDM.DataSetProvider1BeforeGetRecords(Sender: TObject; 
  var OwnerData: OleVariant);
begin
  with Sender as TDataSetProvider do
  begin
    DataSet.Open;
    if not VarIsEmpty(OwnerData) then
      DataSet.Locate('au_id', OwnerData, []) else
    DataSet.First;
  end;
end;

procedure TStatelessRDM.DataSetProvider1AfterGetRecords(Sender: TObject; 
  var OwnerData: OleVariant);
begin
  with Sender as TDataSetProvider do
  begin
    OwnerData := Dataset.FieldValues['au_id'];
    DataSet.Close;
  end;
end;
Here is the code to be implemented on the client:
procedure TForm1.ClientDataSet1BeforeGetRecords(Sender: TObject; 
  var OwnerData: OleVariant);
begin
  // KeyValue is a private OleVariant variable
  if not (Sender as TClientDataSet).Active then
    KeyValue := Unassigned; 
  OwnerData := KeyValue;
end;

procedure TForm1.ClientDataSet1AfterGetRecords(Sender: TObject; 
  var OwnerData: OleVariant);
begin
  KeyValue := OwnerData;
end;

The sample created in this article is available in CodeCentral. Install the server in a MTS package to have maximum effect.

About the Author

Dan Miser is a long-time Delphi user, speciailizing in multi-tier application design and development. He is active on the Borland newsgroups, where he serves as a proud member of TeamB. Dan is a frequent speaker at the Borland Conference, and was a contributing author to "Delphi 5 Developer's Guide".

You can visit his Web site at http://www.distribucon.com/.


Server Response from: ETNASC04