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:

Feedback and questions are welcome on our forum, just as usual.