Mega Code Archive

 
Categories / Delphi / XML
 

SHAMPOO the better SOAP (Made in Delphi)

Title: SHAMPOO - the better SOAP (Made in Delphi) Question: The Delphi approach to distributed computing. Answer: SHAMPOO - the better SOAP (Made in Delphi) Recently a new buzz word came around - SOAP - which stands for Simple Object Access Protocol. While the idea of SOAP is nice - making a platform-independend RPC mechanism over XML - I think it is more like "teaching a new dog old tricks". The problem with SOAP is that it is based on an old paradigm - calling functions in an interface implemented by a distributed component. XML is good for representing data structures in a uniform way, but SOAP tries to make XML encode interfaces while it is far better to just use it for encoding data. SHAMPOO stands for "Simple Hyper Access Meta Protocol - Object Oriented". And OO is what makes the difference. Instead of trying to mimic function calls like SOAP, SHAMPOO's aim is to transfer a component from one place to another - either for in-process or out-of-process communication. Instead of calling functions in a remote object you simply transfer a component to the remote location for processing. The type of processing of course depends of the type of component being sent. As a result of the processing a component is produced which is send back and processed by the calling party. So the only thing both parties need to know about the communication between them is the type of components being exchanged. The actual transfer of the components is acomplished thru component streaming - saving the properties of the component to a stream (either binary or text) and then reading the component back from the stream. Delphi uses this mechanism since version 1 for its form files (.dfm) and in fact everything in Delphi is based on persistent objects and RTTI (run-time type information). With the standarization of XML as a platform-independent text format for data exchange it is very easy to extend the streaming of Delphi components to XML. Now that Borland released Kylix (Delphi for Linux) making programs which work seamlessly on Windows and/or Linux is a trivial task. (Personaly, I think the Delphi text format for representing components is far better than XML - smaller, cleaner and easy to read). So how about a little example in SHAMPOO. Here is the "Hello, World" program ;). unit ShampooDemoMainFormUnit; interface uses SysUtils, Classes, Controls, Forms, StdCtrls; type TShampooDemoMainForm = class (TForm) PersonNameEdit: TEdit; SayHelloButton: TButton; RequestMemo: TMemo; ResponseMemo: TMemo; Label1: TLabel; Label2: TLabel; Label3: TLabel; procedure SayHelloButtonClick(Sender: TObject); end; TPerson = class (TPersistent) protected FPersonName : String; public procedure Assign (aPerson : TPersistent); override; published property PersonName : String read FPersonName write FPersonName; end; TBaseSHAMPOOComponent = class (TComponent); THelloRequest = class (TBaseSHAMPOOComponent) protected FPerson : TPerson; procedure SetPerson (const aPerson : TPerson); public constructor Create (aOwner : TComponent); override; destructor Destroy; override; published property Person : TPerson read FPerson write SetPerson; end; TGreetingResponse = class (TBaseSHAMPOOComponent) protected FGreetingText : String; published property GreetingText : String read FGreetingText write FGreetingText; end; EBaseSHAMPOOException = class (TBaseSHAMPOOComponent); EGreetingError = class (EBaseSHAMPOOException) protected FText : String; public constructor Create (const aText : String); reintroduce; published property Text : String read FText write FText; end; var ShampooDemoMainForm : TShampooDemoMainForm; implementation {$R *.DFM} uses Dialogs; function ComponentToString (const aComponent : TComponent) : String; var aBinStream : TMemoryStream; aStrStream : TStringStream; begin aBinStream := TMemoryStream.Create; try aStrStream := TStringStream.Create (''); try aBinStream.WriteComponent (aComponent); aBinStream.Position := 0; ObjectBinaryToText (aBinStream, aStrStream); aStrStream.Position := 0; Result:= aStrStream.DataString; finally aStrStream.Free; end; finally aBinStream.Free end; end; function StringToComponent (const aString : String) : TComponent; var aStrStream : TStringStream; aBinStream : TMemoryStream; begin aStrStream := TStringStream.Create (aString); try aBinStream := TMemoryStream.Create; try ObjectTextToBinary (aStrStream, aBinStream); aBinStream.Position := 0; Result := aBinStream.ReadComponent (Nil); finally aBinStream.Free; end; finally aStrStream.Free; end; end; function Process (const aComponent : TComponent) : TComponent; begin Result := Nil; try if aComponent is THelloRequest then begin with aComponent as THelloRequest do begin if Person.PersonName = '' then raise EGreetingError.Create ('I do not talk to strangers'); Result := TGreetingResponse.Create (Nil); with Result as TGreetingResponse do begin GreetingText := 'Hello, ' + Person.PersonName + ' !'; end; end; end; except if Assigned (Result) then FreeAndNil (Result); raise; end; end; function RemoteServerProcess (const aString : String) : String; var aRequestComponent : TComponent; aResponseComponent : TComponent; begin aRequestComponent := StringToComponent (aString); try aResponseComponent := Process (aRequestComponent); try Result := ComponentToString (aResponseComponent); finally aResponseComponent.Free; end; finally aRequestComponent.Free; end; end; function CallRemoteProcess (const aComponent : TComponent) : TComponent; var aRequestString : String; aResponseString : String; begin aRequestString := ComponentToString (aComponent); ShampooDemoMainForm.RequestMemo.Lines.Text := aRequestString; // for the example only aResponseString := RemoteServerProcess (aRequestString); ShampooDemoMainForm.ResponseMemo.Lines.Text := aResponseString; // for the example only Result := StringToComponent (aResponseString); end; constructor EGreetingError.Create (const aText : String); begin inherited Create (Nil); FText := aText; end; procedure TPerson.Assign (aPerson : TPersistent); begin if aPerson is TPerson then begin with aPerson as TPerson do begin Self.PersonName := PersonName; end; end else inherited; end; constructor THelloRequest.Create (aOwner : TComponent); begin inherited; FPerson := TPerson.Create; end; destructor THelloRequest.Destroy; begin FPerson.Free; inherited; end; procedure THelloRequest.SetPerson (const aPerson : TPerson); begin FPerson.Assign (aPerson); end; procedure TShampooDemoMainForm.SayHelloButtonClick(Sender: TObject); var aHelloRequest : THelloRequest; begin aHelloRequest := THelloRequest.Create (Nil); try aHelloRequest.Person.PersonName := PersonNameEdit.Text; with CallRemoteProcess (aHelloRequest) as TGreetingResponse do try ShowMessage (GreetingText); finally Free; end; finally aHelloRequest.Free; end; end; initialization RegisterClasses ([THelloRequest, TGreetingResponse, EGreetingError]); end. What I haven't showed is how to stream the exceptions but this is a trivial task. So, in order to make SHAMPOO cross-platfrom and language independent you can easily substitute the "ComponentToString" and "StringToComponent" functions (which I ripped off from the Delphi Help) with "ComponentToXML" and "XMLToComponent" which you can find on the Borland CodeCentral site (http://community.borland.com) or (www.rapware.com). You can use any transport protocol to transffer the text - HTTP, CORBA, Sockets, SMTP. Any opinions and suggestions are highly appreciated. (rossen_assenov@yahoo.com)