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.
