The shared contract
First, you'll find a common unit, shared by both client and server applications:
unit Project14Interface;
interface
type ICalculator = interface(IInvokable) ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}'] function Add(n1,n2: integer): integer; end;
const ROOT_NAME = 'service'; APPLICATION_NAME = 'RestService';
implementation
end.
Unique purpose of this unit is to define the service interface
,
and the ROOT_NAME
used for the ORM Model (and therefore RESTful
URI scheme), and the APPLICATION_NAME
used for named-pipe
communication.
The server sample application
The server is implemented as such:
program Project14Server;
{$APPTYPE CONSOLE}
uses SysUtils, SQLite3Commons, SQLite3, Project14Interface;
type TServiceCalculator = class(TInterfacedObject, ICalculator) public function Add(n1,n2: integer): integer; end;
function TServiceCalculator.Add(n1, n2: integer): integer; begin result := n1+n2; end;
var aModel: TSQLModel; begin aModel := TSQLModel.Create([],ROOT_NAME); try with TSQLRestServerDB.Create(aModel,ChangeFileExt(paramstr(0),'.db'),true) do try CreateMissingTables; // we need AuthGroup and AuthUser tables ServiceRegister(TServiceCalculator,[TypeInfo(ICalculator)],sicShared); if ExportServerNamedPipe(APPLICATION_NAME) then writeln('Background server is running.'#10) else writeln('Error launching the server'#10); write('Press [Enter] to close the server.'); readln; finally Free; end; finally aModel.Free; end; end.
It will instantiate a TSQLRestServerDB
class, containing a
SQLite3 database engine. In fact, since we need authorization, both
AuthGroup
and AuthUser
tables are expected to be
available.
Then a call to ServiceRegister()
will define the
ICalculator
contract, and the TServiceCalculator
class to be used as its implementation. The sicShared
mode is
used, since the same implementation class can be shared during all calls (there
is no shared nor private data to take care).
The client sample application
The client is just a simple form with two edit fields, and a "Call" button, which event is implemented as such:
procedure TForm1.btnCallClick(Sender: TObject); var a,b: integer; err: integer; I: ICalculator; begin val(edtA.Text,a,err); if err<>0 then begin edtA.SetFocus; exit; end; val(edtB.Text,b,err); if err<>0 then begin edtB.SetFocus; exit; end; if Client=nil then begin if Model=nil then Model := TSQLModel.Create([],ROOT_NAME); Client := TSQLRestClientURINamedPipe.Create(Model,APPLICATION_NAME); Client.SetUser('User','synopse'); Client.ServiceRegister([TypeInfo(ICalculator)],sicShared); end; if Client.Services['Calculator'].Get(I) then lblResult.Caption := IntToStr(I.Add(a,b)); end;
The client code is initialized as such:
- A TSQLRestClientURINamedPipe
instance is created, with an
associate TSQLModel
and the given APPLICATION_NAME
to
access the proper server;
- The connection is authenticated with the default 'User'
rights;
- The ICalculator
interface is defined in the client's internal
factory, in sicShared
mode (just as in the server).
Once the client is up and ready, the local I: ICalculator
variable instance is retrieved, and the remote service is called directly via a
simple I.Add(a,b)
statement.
You can imagine how easy and safe it will be to implement a Service Oriented Architecture for your future applications, using mORMot.
Continue reading...
This article is part of a list of other blog articles, extracted from the official Synopse mORMot framework documentation:- Interface based services;
- Defining a data contract;
- Service side implementation;
- Using services on the Client or Server sides;
- Interface based services implementation details;
- WCF, mORMot and Event Sourcing.
Feedback and questions are welcome on our forum, just as usual.