Delphi / FreePascal client samples

The "27 - CrossPlatform ClientsRegressionTests.dpr" sample creates a mORMot server with its own ORM data model, containing a TSQLRecordPeople class, and a set of interface-based SOA services, some including complex types like a record.

Then this sample uses a generated mORMotClient.pas, retrieved from the "download as file" link of the CrossPlatform template above.
Its set of regression tests (written using a small cross-platform TSynTest unit test class) will then perform remote ORM and SOA calls to the PeopleServer embedded instance, over all supported authentication schemes - see Authentication:

 Cross Platform Units for mORMot
---------------------------------

1. Running "Iso8601DateTime" 30003 tests passed in 00:00:018 2. Running "Base64Encoding" 304 tests passed in 00:00:000 3. Running "JSON" 18628 tests passed in 00:00:056 4. Running "Model" 1013 tests passed in 00:00:003 5. Running "Cryptography" 4 tests passed in 00:00:000
Tests failed: 0 / 49952 Time elapsed: 00:00:080
Cross Platform Client for mORMot without authentication ---------------------------------------------------------
1. Running "Connection" 2 tests passed in 00:00:010 2. Running "ORM" 4549 tests passed in 00:00:160 3. Running "ORMBatch" 4564 tests passed in 00:00:097 4. Running "Services" 26253 tests passed in 00:00:302 5. Running "CleanUp" 1 tests passed in 00:00:000
Tests failed: 0 / 35369 Time elapsed: 00:00:574
Cross Platform Client for mORMot using TSQLRestServerAuthenticationNone ------------------------------------------------------------------------- ...
Cross Platform Client for mORMot using TSQLRestServerAuthenticationDefault ---------------------------------------------------------------------------- ...

The generated mORMotClient.pas unit is used for all "Cross Platform Client" tests above, covering both ORM and SOA features of the framework.

Connection to the server

You could manually connect to a mORMot server as such:

var Model: TSQLModel;
    Client: TSQLRestClientHTTP;
...
  Model := TSQLModel.Create([TSQLAuthUser,TSQLAuthGroup,TSQLRecordPeople]);
  Client := TSQLRestClientHTTP.Create('localhost',SERVER_PORT,Model);
  if not Client.Connect then
    raise Exception.Create('Impossible to connect to the server');
  if Client.ServerTimeStamp=0 then
    raise Exception.Create('Incorrect server');
  if not Client.SetUser(TSQLRestAuthenticationDefault,'User','synopse') then
    raise Exception.Create('Impossible to authenticate to the server');
...

Or you may use the GetClient() function generated in mORMotClient.pas:

/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials - on connection or authentication error,
// this function will raise a corresponding exception
function GetClient(const aServerAddress, aUserName,aPassword: string;
  aServerPort: integer=SERVER_PORT): TSQLRestClientHTTP;

Which could be used as such:

var Client: TSQLRestClientHTTP;
...
  Client := GetClient('localhost','User','synopse')

The data model and the expected authentication scheme were included in the GetClient() function, which will raise the expected ERestException in case of any connection or authentication issue.

CRUD/ORM remote access

Thanks to SynCrossPlatform* units, you could easily perform any remote ORM operation on your mORMot server, with the usual TSQLRest CRUD methods.
For instance, the RegressionTests.dpr sample performs the following operations

  fClient.CallBackGet('DropTable',[],Call,TSQLRecordPeople); // call of method-based service
  check(Call.OutStatus=HTML_SUCCESS);
  people := TSQLRecordPeople.Create; // create a record ORM
  try
    for i := 1 to 200 do begin
      people.FirstName := 'First'+IntToStr(i);
      people.LastName := 'Last'+IntToStr(i);
      people.YearOfBirth := i+1800;
      people.YearOfDeath := i+1825;
      people.Sexe := TPeopleSexe(i and 1);
      check(Client.Add(people,true)=i); // add one record
    end;
  finally
    people.Free;
  end;
...
  people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'',
    'yearofbirth=?',[1900]); // parameterized query returning one or several rows
  try
    n := 0;
    while people.FillOne do begin
      inc(n);
      check(people.ID=100);
      check(people.FirstName='First100');
      check(people.LastName='Last100');
      check(people.YearOfBirth=1900);
      check(people.YearOfDeath=1925);
    end;
    check(n=1); // we expected only one record here
  finally
    people.Free;
  end;
  for i := 1 to 200 do
    if i and 15=0 then
      fClient.Delete(TSQLRecordPeople,i) else // record deletion
    if i mod 82=0 then begin
      people := TSQLRecordPeople.Create;
      try
        id := i+1;
        people.ID := i;
        people.YearOfBirth := id+1800;
        people.YearOfDeath := id+1825;
        check(fClient.Update(people,'YEarOFBIRTH,YEarOfDeath')); // record modification
      finally
        people.Free;
      end;
    end;
  for i := 1 to 200 do begin
    people := TSQLRecordPeople.Create(fClient,i); // retrieve one instance from ID
    try
      if i and 15=0 then // was deleted
        Check(people.ID=0) else begin
        if i mod 82=0 then
          id := i+1 else // was modified
          id := i;
        Check(people.ID=i);
        Check(people.FirstName='First'+IntToStr(i));
        Check(people.LastName='Last'+IntToStr(i));
        Check(people.YearOfBirth=id+1800);
        Check(people.YearOfDeath=id+1825);
        Check(ord(people.Sexe)=i and 1);
      end;
    finally
      people.Free;
    end;
  end;

As we already stated, BATCH mode is also supported, with the classic mORMot syntax:

...
    res: TIntegerDynArray;
...
  fClient.BatchStart(TSQLRecordPeople);
  people := TSQLRecordPeople.Create;
  try
    for i := 1 to 200 do begin
      people.FirstName := 'First'+IntToStr(i);
      people.LastName := 'Last'+IntToStr(i);
      people.YearOfBirth := i+1800;
      people.YearOfDeath := i+1825;
      fClient.BatchAdd(people,true);
    end;
  finally
    people.Free;
  end;
  fClient.fBatchSend(res)=HTML_SUCCESS);
  check(length(res)=200);
  for i := 1 to 200 do
    check(res[i-1]=i); // server returned the IDs of the newly created records

Those BatchAdd / BatchDelete / BatchUpdate methods of TSQLRest have the benefit to introduce at client level:

  • Much higher performance, especially on multi-insertion or multi-update of data;
  • Transactional support: TSQLRest.BatchStart() has an optional AutomaticTransactionPerRow parameter, set to 10000 by default, which will create a server-side transaction during the write process, and an ACID rollback in case of any failure.

You can note that all above code has exactly the same structure and methods than standard mORMot clients.

The generated mORMotClient.pas unit contains all needed TSQLRecord types, and its used properties, including enumerations or complex records. The only dependency of this unit are SynCrossPlatform* units, so would be perfectly cross-platform (whereas our main SynCommons.pas and mORMot.pas units do target only Win32 and Win64).

As a result, you are able to share server and client code between a Windows project and any supported platform, even AJAX (see "Smart Mobile Studio client samples" below). A shared unique code base would eventually reduce both implementation and debugging time, which is essential to unleash your business code potential and maximize your ROI.

Service consumption

The ultimate goal of the mORMot framework is to publish your business via a Service-Oriented Architecture (SOA).
As a consequence, those services should be made available from any kind of device or platform, even outside the Windows world. The server is able to generate client wrappers code, which could be used to consume any Client-Server services via interfaces using any supported authentication scheme - see Authentication.

Here is an extract of the mORMotClient.pas unit as generated for the RegressionTests.dpr sample:

type
  /// service implemented by TServiceCalculator
  // - you can access this service as such:
  // !var aCalculator: ICalculator;
  // !begin
  // !   aCalculator := TCalculator.Create(aClient);
  // !   // now you can use aCalculator methods
  // !...
  ICalculator = interface(IServiceAbstract)
    ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
    function Add(const n1: integer; const n2: integer): integer;
    procedure ToText(const Value: currency; const Curr: string; var Sexe: TPeopleSexe; var Name: string);
    function RecordToText(var Rec: TTestCustomJSONArraySimpleArray): string;
  end;

/// implements ICalculator from http://localhost:888/root/Calculator // - this service will run in sicShared mode TServiceCalculator = class(TServiceClientAbstract,ICalculator) public constructor Create(aClient: TSQLRestClientURI); override; function Add(const n1: integer; const n2: integer): integer; procedure ToText(const Value: currency; const Curr: string; var Sexe: TPeopleSexe; var Name: string); function RecordToText(var Rec: TTestCustomJSONArraySimpleArray): string; end;

As you can see, a dedicated class has been generated to consume the server-side ICalculator interface-based service, in its own ICalculator client-side type.
It is able to handle complex types, like enumerations (e.g. TPeopleSexe) and records (e.g. TTestCustomJSONArraySimpleArray), which are also defined in the very same mORMotClient.pas unit.
You can note that the RawUTF8 type has been changed into the standard Delphi / FreePascal string type, since it is the native type used by our SynCrossPlatformJSON.pas unit for all its JSON marshaling. Of course, under latest version of Delphi and FreePascal, this kind of content may be Unicode encoded (either as UTF-16 for the string = UnicodeString Delphi type, or as UTF-8 for the FreePascal / Lazarus string type).

The supplied regression tests show how to use remotely those services:

var calc: ICalculator;
    i,j: integer;
    sex: TPeopleSexe;
    name: string;
...
  calc := TServiceCalculator.Create(fClient);
  check(calc.InstanceImplementation=sicShared);
  check(calc.ServiceName='Calculator');
  for i := 1 to 200 do
    check(calc.Add(i,i+1)=i*2+1);
  for i := 1 to 200 do begin
    sex := TPeopleSexe(i and 1);
    name := 'Smith';
    calc.ToText(i,'$',sex,name);
    check(sex=sFemale);
    check(name=format('$ %d for %s Smith',[i,SEX_TEXT[i and 1]]));
  end;
...

As with regular mORMot client code, a TServiceCalculator instance is created and is assigned to a ICalculator local variable. As such, no try ... finally Calc.Free end block is mandatory here, to avoid any memory leak: the compiler will create such an hidden block for the Calc: ICalculator variable scope.

The service-side contract of the ICalculator signature is retrieved and checked within TServiceCalculator.Create, and would raise an ERestException if it does not match the contract identified in mORMotClient.pas.

The cross-platform clients are able to manage the service instance life-time, especially the sicPerClient mode. In this case, an implementation class instance will be created on the server for each client, until the corresponding interface instance will released (i.e. out of scope or assigned to nil), which will release the server-side instance - just like with a regular mORMot client code.

Note that all process here is executed synchronously, i.e. in blocking mode. It is up to you to ensure that your application is able to still be responsive, even if the server does a lot of process, so may be late to answer. A dedicated thread may help in this case.