Mega Code Archive

 
Categories / Delphi / Forms
 

Simple high performance 3 tier apps with Indy and ADO

Title: Simple high performance 3-tier apps with Indy and ADO Question: Now it's easy to create simple high performance middleware applications with Indy and ADO (thanks to OLE-DB resource poooling). Answer: First of all sorry for my bad english. You can read the post of Romeo Lefter (seems to be romanian like me) on this site about this problem, but I'll use a diffrent aproach. I'll skip the intro and bla..bla part (thanks to Romeo). What we need: Indy (www.nevrona.com/indy, VCL comps for easy TCP/IP communications) kbmMemtable (www.components4developers.com, advanced in-memory table), ADOExpress (Ado component suite included in last version of Delphi and CB) or Adonis (which I prefer). These examples are based on Adonis but are very easy to convert to AdoExpress (the diffrences between them are: ADOExpress has a component called AdoConnection; Adonis has AdoDatabase; the same thing but with diffrent name). We'll use the same aproach as Internet Explorer: connect/send request/receive responese(data)/disconnect. This model is very scalable and very efficient. We have following tiers: the client named CL (developed by us), middleware server named MD (developed by us) and database server named DB (can be any SQL database which has a OLE-DB provider with resource pooling - MS SQL SERVER, Oracle, Interbase(http://www.lcpi.lipetsk.ru/prog/eng/, Lipetsk provider)). What is "resource pooling": you can connect to a db server thru a OLE-DB provider; the second connection to db server is very quick because the first call creates a pool of connections available for this process. This example is based on MS SQL Server. CL connects MD (thru Indy), CL sends to MD the sql query/statement, MD connects DB (thru ADO - very quick thanks to resource pooling), DB executes the request and sends response to MD, MD closes the connection to DB and sends the response to CL, which closes the connection to MD. In case of errors an exception is send back. You can do custom RPC (remote procedure call). An example: when the CL sends the sql query/statement, the statement can look somethink like this: "@SEND_MAIL", and the MD executes a custom code (in this case sending mails) and not pass to DB (because it has @ in front of the string). The use of TidThreadPoolMgr is very useful because the threads are maintained in a pool and can be used for thousands of requests. A normal thing to do on MD server is to create dinamically (for every request) a ADOConnection or ADODatabase, a ADOQuery, a kbmMemtable, a VCLZip component if your response to be sent zipped. To improve the performance a little and the quality of code we create a TSQLThread class with BeforeExecute and AfterExecute procedures to override the standard way (here we can create our components - only once at creation and destruction of thread). YES, the BeforeExecute and AfterExecute are executed once: at the creation and destruction. MD server: ------------------------------------------------------------------------- unit uMain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, IdThreadMgr, IdThreadMgrPool, IdBaseComponent, IdComponent, IdTCPServer, ADODS, Db, kbmMemTable; type TSQLThread = class(TIdPeerThread) protected FADODb: TADODatabase; FADOQry: TADOQuery; Fmt: TkbmMemTable; procedure BeforeExecute; override; procedure AfterExecute; override; public end; TForm1 = class(TForm) TCPServer: TIdTCPServer; IdThreadMgrPool: TIdThreadMgrPool; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure TCPServerExecute(AThread: TIdPeerThread); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses ActiveX, ComObj; {$R *.DFM} procedure TSQLThread.BeforeExecute; begin CoInitialize(nil); //initializarea COM; it's required for every thread FADODb := TADODatabase.Create(nil); FADODb.DatabaseConnect := 'Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=pubs;Data Source=CAM42'; FADOQry := TADOQuery.Create(nil); FADOQry.Database := FADODb; FADOQry.ParamCheck := False; Fmt := TkbmMemTable.Create(nil); end; procedure TSQLThread.AfterExecute; begin if Assigned(FADOQry) then begin FADOQry.Active:=False; FreeAndNIL(FADOQry); end; if Assigned(FADODb) then begin FADODb.Connected:=False; FreeAndNil(FADODb); end; if Assigned(Fmt) then begin FreeAndNil(Fmt); end; CoUnInitialize; end; procedure TForm1.FormCreate(Sender: TObject); begin TCPServer.ThreadClass:=TSQLThread; //assign the TSQLThread class TCPServer.Active := True; end; procedure TForm1.FormDestroy(Sender: TObject); begin TCPServer.Active := False; end; procedure TForm1.TCPServerExecute(AThread: TIdPeerThread); var stream: TMemoryStream; begin with AThread.Connection do begin stream := TMemoryStream.Create; try try with TSQLThread(AThread) do begin FADOQry.SQL.Text := ReadLn; //reads the sql command from socket FADODb.Connected := True; FADOQry.Open; with Fmt do begin LoadFromDataSet(FADOQry, [mtcpoStructure,mtcpoOnlyActiveFields,mtcpoProperties,mtcpoLookup,mtcpoCalculated]); //loads records returned by DB into memory table Active := True; SaveToStream(stream, [mtfSaveData, mtfSaveCalculated,mtfSaveLookup,mtfSaveNonVisible,mtfSaveBlobs,mtfSaveDef,mtfSaveIndexDef,mtfSkipRest,mtfSaveFiltered,mtfSaveInLocalFormat]); //saves the records in to a stream which can be optionally zipped end; FADOQry.Close; FADODb.Connected := False; end; WriteStream(stream); //writes the data to CL Disconnect; with TSQLThread(AThread).Fmt do begin EmptyTable; Active := False; end; except //if any errors end; finally stream.Free; end; end; end; end. --------------------------------------------------------------------------- CL: --------------------------------------------------------------------------- unit client; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, StdCtrls, Grids, DBGrids, Db, kbmMemTable; type TForm1 = class(TForm) tblc: TkbmMemTable; DataSource1: TDataSource; DBGrid1: TDBGrid; Button1: TButton; tcp: TIdTCPClient; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var stream: TMemoryStream; begin stream:=TMemoryStream.Create; try tcp.Connect; tcp.WriteLn('select * from authors'); tcp.ReadStream(stream,-1,true); stream.Position:=0; tblc.LoadFromStream(stream); tcp.Disconnect; finally stream.Free; end; end; end. ----------------------------------------------------------------------------- The advantages: very high performance; scalability; can be used on multiple computers(like a cluster); instant deployment of client software;.... This is very easy model to resolve very complicated things. In the future (max 2 months) I'll make a set of componenets (under open source) for easy 3-tier development. In adition to those comps I'll make a ISAPI dll to make this arhitecture ready for Internet (this ISAPI dll is a trick very large used to pass firewalls). These examples are made in 15 min. All I want to know if the community wants such components? Thank you.