mORMot and Open Source friends
Check-in [cd20687e28]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:{1387} introducing TSQLRest.ServiceContainer function to allow injection of any resolver, needed e.g. to register CQRS Persistence Factories like aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true) also let Resolve() be able to retrieve ancestor interfaces on a given TInterfaceResolverForSingleInterface instance, if needed (e.g. a single class would implement IDomUserCommand and IDomUserQuery
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: cd20687e280ffe5f1b5f1a0379bc9b8d0017a168
User & Date: ab 2015-05-21 10:35:43
Context
2015-05-21
10:45
{1388} fixed unexpected compilation error in some versions of the Delphi compiler check-in: e77bb0effb user: ab tags: trunk
10:35
{1387} introducing TSQLRest.ServiceContainer function to allow injection of any resolver, needed e.g. to register CQRS Persistence Factories like aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true) also let Resolve() be able to retrieve ancestor interfaces on a given TInterfaceResolverForSingleInterface instance, if needed (e.g. a single class would implement IDomUserCommand and IDomUserQuery check-in: cd20687e28 user: ab tags: trunk
10:31
{1386} made TSQLRestServerURIContext.FillInput public to allow direct use of InputPairs[] check-in: 576d567eb0 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/DDD/infra/dddInfraRepoUser.pas.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
...
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
...
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
...
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
...
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
{ *********** Implements User Aggregate CQRS Repository via mORMot's RESTful ORM }

type
  /// implements a User CQRS Repository via mORMot's RESTful ORM
  // - this class will use a supplied TSQLRest instance to persist TUser
  // Aggregate Roots, following the IDomUserCommand CQRS methods
  // - each TUser aggregate will be mapped into a TSQLRecordUser ORM table
  TInfraRepoUser = class(TDDDRepositoryRestCommand,IDomUserCommand)
  public
    function SelectByLogonName(const aLogonName: RawUTF8): TCQRSResult;
    function SelectByEmailValidation(aValidationState: TDomUserEmailValidation): TCQRSResult;
    function SelectByLastName(const aName: TLastName; aStartWith: boolean): TCQRSResult;
    function Get(out aAggregate: TUser): TCQRSResult;
    function GetAll(out aAggregates: TUserObjArray): TCQRSResult;
    function GetNext(out aAggregate: TUser): TCQRSResult;
................................................................................
begin
  inherited Create(IDomUserCommand,TInfraRepoUser,TUser,aRest,TSQLRecordUser,aOwner);
  AddFilterOrValidate(['*'],TSynFilterTrim.Create);
  AddFilterOrValidate(['LogonName'],TSynValidateNonVoidText.Create);
end;

class procedure TInfraRepoUserFactory.RegressionTests(test: TSynTestCase);
var Command: TDDDRepositoryRestFactory;
procedure TestOne;
const MAX=1000;
      MOD_EMAILVALID=ord(high(TDomUserEmailValidation))+1;
var cmd: IDomUserCommand;
    qry: IDomUserQuery;
    user: TUser;
    users: TUserObjArray;
    i: integer;
    itext: RawUTF8;
    v: TDomUserEmailValidation;
    count: array[TDomUserEmailValidation] of integer;
    msg: string;
begin
  test.Check(Command.GetOneInstance(cmd));
  user := TUser.Create;
  try
    for i := 1 to MAX do begin
      UInt32ToUtf8(i,itext);
      user.LogonName := '  '+itext; // left '  ' to test TSynFilterTrim.Create
      user.EmailValidated := TDomUserEmailValidation(i mod MOD_EMAILVALID);
      user.Name.Last := 'Last'+itext;
................................................................................
    end;
    test.check(cmd.Commit=cqrsSuccess);
  finally
    user.Free;
  end; 
  user := TUser.Create;
  try
    test.Check(Command.GetOneInstance(qry));
    test.Check(qry.GetCount=0);
    for i := 1 to MAX do begin
      UInt32ToUtf8(i,itext);
      test.Check(qry.SelectByLogonName(itext)=cqrsSuccess);
      test.Check(qry.GetCount=1);
      test.Check(qry.Get(user)=cqrsSuccess);
      test.Check(qry.GetCount=1);
................................................................................
      test.Check(user.EmailValidated=TDomUserEmailValidation(i mod MOD_EMAILVALID));
      test.Check(user.Name.Last='Last'+itext);
      test.Check(user.Name.First='First'+itext);
      test.Check(user.Address.Street1='Street '+itext);
      test.Check(user.Address.Country.Alpha2='FR');
      test.Check(user.Phone1=itext);
    end;
    test.Check(Command.GetOneInstance(cmd));
    try
      for v := low(TDomUserEmailValidation) to high(TDomUserEmailValidation) do begin
        test.Check(cmd.SelectByEmailValidation(v)=cqrsSuccess);
        ObjArrayClear(users); // should be done, otherwise memory leak
        test.Check(cmd.GetAll(users)=cqrsSuccess);
        test.Check(length(users)>=MAX div MOD_EMAILVALID);
        count[v] := length(users);
................................................................................
        end;
      end;
      test.Check(cmd.DeleteAll=cqrsSuccess,'delete all evFailed');
      test.check(cmd.Commit=cqrsSuccess);
    finally
      ObjArrayClear(users);
    end;
    test.Check(Command.GetOneInstance(cmd));
    for v := low(TDomUserEmailValidation) to high(TDomUserEmailValidation) do begin
      test.Check(cmd.SelectByEmailValidation(v)=cqrsSuccess);
      if v=evFailed then
        test.Check(cmd.GetCount=0) else
        test.Check(cmd.GetCount=count[v]);
      i := 0;
      while cmd.GetNext(user)=cqrsSuccess do begin
................................................................................
    test.check(cmd.GetLastError=cqrsDDDValidationFailed);
    msg := cmd.GetLastErrorInfo.msg;
    test.check(pos('TUser.LogonName',msg)>0,msg);
  finally
    user.Free;
  end;
end;
var Rest: TSQLRest;
begin
  Rest := TSQLRestServerFullMemory.CreateWithOwnModel([TSQLRecordUser]);
  try
    Command := Create(Rest);
    try
      TestOne; // sub function to ensure that all I*Command are released
    finally
      Command.Free;
    end;
  finally
    Rest.Free;
  end;
end;


end.






|







 







|













|







 







|







 







|







 







|







 







<



|
<
|
<
<
<







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
...
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
...
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
...
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
...
326
327
328
329
330
331
332

333
334
335
336

337



338
339
340
341
342
343
344
{ *********** Implements User Aggregate CQRS Repository via mORMot's RESTful ORM }

type
  /// implements a User CQRS Repository via mORMot's RESTful ORM
  // - this class will use a supplied TSQLRest instance to persist TUser
  // Aggregate Roots, following the IDomUserCommand CQRS methods
  // - each TUser aggregate will be mapped into a TSQLRecordUser ORM table
  TInfraRepoUser = class(TDDDRepositoryRestCommand,IDomUserCommand,IDomUserQuery)
  public
    function SelectByLogonName(const aLogonName: RawUTF8): TCQRSResult;
    function SelectByEmailValidation(aValidationState: TDomUserEmailValidation): TCQRSResult;
    function SelectByLastName(const aName: TLastName; aStartWith: boolean): TCQRSResult;
    function Get(out aAggregate: TUser): TCQRSResult;
    function GetAll(out aAggregates: TUserObjArray): TCQRSResult;
    function GetNext(out aAggregate: TUser): TCQRSResult;
................................................................................
begin
  inherited Create(IDomUserCommand,TInfraRepoUser,TUser,aRest,TSQLRecordUser,aOwner);
  AddFilterOrValidate(['*'],TSynFilterTrim.Create);
  AddFilterOrValidate(['LogonName'],TSynValidateNonVoidText.Create);
end;

class procedure TInfraRepoUserFactory.RegressionTests(test: TSynTestCase);
var Rest: TSQLRestServer;
procedure TestOne;
const MAX=1000;
      MOD_EMAILVALID=ord(high(TDomUserEmailValidation))+1;
var cmd: IDomUserCommand;
    qry: IDomUserQuery;
    user: TUser;
    users: TUserObjArray;
    i: integer;
    itext: RawUTF8;
    v: TDomUserEmailValidation;
    count: array[TDomUserEmailValidation] of integer;
    msg: string;
begin
  test.Check(Rest.Services.Resolve(IDomUserCommand,cmd));
  user := TUser.Create;
  try
    for i := 1 to MAX do begin
      UInt32ToUtf8(i,itext);
      user.LogonName := '  '+itext; // left '  ' to test TSynFilterTrim.Create
      user.EmailValidated := TDomUserEmailValidation(i mod MOD_EMAILVALID);
      user.Name.Last := 'Last'+itext;
................................................................................
    end;
    test.check(cmd.Commit=cqrsSuccess);
  finally
    user.Free;
  end; 
  user := TUser.Create;
  try
    test.Check(Rest.Services.Resolve(IDomUserQuery,qry));
    test.Check(qry.GetCount=0);
    for i := 1 to MAX do begin
      UInt32ToUtf8(i,itext);
      test.Check(qry.SelectByLogonName(itext)=cqrsSuccess);
      test.Check(qry.GetCount=1);
      test.Check(qry.Get(user)=cqrsSuccess);
      test.Check(qry.GetCount=1);
................................................................................
      test.Check(user.EmailValidated=TDomUserEmailValidation(i mod MOD_EMAILVALID));
      test.Check(user.Name.Last='Last'+itext);
      test.Check(user.Name.First='First'+itext);
      test.Check(user.Address.Street1='Street '+itext);
      test.Check(user.Address.Country.Alpha2='FR');
      test.Check(user.Phone1=itext);
    end;
    test.Check(Rest.Services.Resolve(IDomUserCommand,cmd));
    try
      for v := low(TDomUserEmailValidation) to high(TDomUserEmailValidation) do begin
        test.Check(cmd.SelectByEmailValidation(v)=cqrsSuccess);
        ObjArrayClear(users); // should be done, otherwise memory leak
        test.Check(cmd.GetAll(users)=cqrsSuccess);
        test.Check(length(users)>=MAX div MOD_EMAILVALID);
        count[v] := length(users);
................................................................................
        end;
      end;
      test.Check(cmd.DeleteAll=cqrsSuccess,'delete all evFailed');
      test.check(cmd.Commit=cqrsSuccess);
    finally
      ObjArrayClear(users);
    end;
    test.Check(Rest.Services.Resolve(IDomUserCommand,cmd));
    for v := low(TDomUserEmailValidation) to high(TDomUserEmailValidation) do begin
      test.Check(cmd.SelectByEmailValidation(v)=cqrsSuccess);
      if v=evFailed then
        test.Check(cmd.GetCount=0) else
        test.Check(cmd.GetCount=count[v]);
      i := 0;
      while cmd.GetNext(user)=cqrsSuccess do begin
................................................................................
    test.check(cmd.GetLastError=cqrsDDDValidationFailed);
    msg := cmd.GetLastErrorInfo.msg;
    test.check(pos('TUser.LogonName',msg)>0,msg);
  finally
    user.Free;
  end;
end;

begin
  Rest := TSQLRestServerFullMemory.CreateWithOwnModel([TSQLRecordUser]);
  try
    Rest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(Rest)],true);

    TestOne; // sub function to ensure that all I*Command are released



  finally
    Rest.Free;
  end;
end;


end.

Changes to SQLite3/Documentation/Synopse SQLite3 Framework.pro.

13384
13385
13386
13387
13388
13389
13390
13391

13392
13393
13394
13395
13396
13397
13398
.....
13431
13432
13433
13434
13435
13436
13437

13438
13439
13440
13441
13442
13443
13444
.....
13456
13457
13458
13459
13460
13461
13462























13463
13464
13465
13466
13467
13468
13469
13470
13471
13472
13473
13474
13475
13476

13477
13478
13479
13480
13481
13482
13483
.....
13525
13526
13527
13528
13529
13530
13531
13532
13533
13534
13535
13536
13537
13538
13539
!        raise EMyApplicationException.CreateFmt('Commit error: %s',[cmd.GetLastErrorInfo]);
!    // here everything has been written to the database
!  finally
!    user.Free;
!  end;
This {\i dual-phase} commit appears to be a clean way of implement the @100@. Under the hood, when used with our ORM - as we will now explain - {\i @*Unit Of Work@} will be expressed as a {\f1\fs20 I*Command} service, uncoupled from the persistence layer it runs on.
:    Automated Repository using the ORM
As you may have noticed, we did just implemented the {\f1\fs20 interface} types we needed. That is, we have the {\i contract} of our persistence services, but no actual implementation of it. As such, those {\f1\fs20 interface} definitions are useless. Luckily for use, the {\f1\fs20 mORMotDDD.pas} unit offers an easy way to implement those using @3@, with minimal coding.

First we would need to map our domain object (i.e. our {\f1\fs20 TUser} instance and its properties) into a {\f1\fs20 TSQLRecord}. We may do it by hand, but you may find an handy way. Just run the following in the context of your application:
! TDDDRepositoryRestFactory.ComputeSQLRecord(TUser);
This {\f1\fs20 class procedure} would create a {\f1\fs20 ddsqlrecord.inc} file in the executable folder, containing the needed field definition, with one {\f1\fs20 TSQLRecord} type corresponding to each hierarchy level of the original {\f1\fs20 TPersistent} definition. Nested fields would be defined as a single column in the {\f1\fs20 TSQLRecord}, e.g. {\f1\fs20 Address.Country.Iso} would be flattened as a {\f1\fs20 Address_Country} property.
So if we follow the class hierarchy, we would have:
\graph DDDCQRSORMMapping CQRS Class Hierarchy Mapping for ORM and DDD Entities
\TPerson\TPersonContactable
\TPersonContactable\TUser
................................................................................
!    property LogonName: RawUTF8 read fLogonName write fLogonName;
!    property EmailValidated: TDomUserEmailValidation read fEmailValidated write fEmailValidated;
!  end;
In practice, the following property would need to be tuned as such:
!    property LogonName: RawUTF8 read fLogonName write fLogonName
!!      stored AS_UNIQUE;
Take a look at the {\f1\fs20 dddInfraRepoUser.pas} and {\f1\fs20 dddDomUserTypes.pas} units to make a comparison between the DDD objects and their corresponding {\f1\fs20 TSQLRecord*} types.

Since the generated {\f1\fs20 TSQLRecordUser} type follows known conventions, the {\f1\fs20 mORMotDDD.pas} unit is able to do almost all the persistence work in an automated way, by inheriting of two classes:
- Defining a {\i Repository Factory} (i.e. a class able to generate {\f1\fs20 IDomUserQuery} or {\f1\fs20 IDomUserCommand} instances on requests) by inheriting from {\f1\fs20 TDDDRepositoryRestFactory}
- Defining the actual {\f1\fs20 IDomUserCommand} methods by inheriting from {\f1\fs20 TDDDRepositoryRestCommand}, and using high level protected methods to access the {\f1\fs20 TUser} from internal {\f1\fs20 TSQLRecordUser} ORM values.
First of all, we define the Factory:
!type
!  TInfraRepoUserFactory = class(TDDDRepositoryRestFactory)
!  public
................................................................................
- We would like to implement a {\f1\fs20 IDomUserCommand} contract - and, by the way, implement also its parent {\f1\fs20 IDomUserQuery interface};
- The actual implementation class would be {\f1\fs20 TInfraRepoUser} - which would be defined just after;
- The {\i Aggregate/Entity} class is a {\f1\fs20 TUser} kind of object;
- The associated {\f1\fs20 TSQLRest} server would be the one supplied to this class;
- The ORM class, defining the actual SQL table or NoSQL collection which would store the data, is {\f1\fs20 TSQLRecordUser};
- An optional {\f1\fs20 TDDDRepositoryRestManager} instance may be supplied as owner of this factory - but it is not used in most cases.
The {\f1\fs20 AddFilterOrValidate()} method allows to set some @56@ expectations at DDD level. Those rules would be applied before {\f1\fs20 Commit} would take place, without any use of the ORM rules. In the above code, {\f1\fs20 TSynFilterTrim} would remove any space from all text fields of the {\f1\fs20 TUser} instance, and {\f1\fs20 TSynValidateNonVoidText} will ensure that the {\f1\fs20 TUser.LogonName} field would not be {\f1\fs20 ''} - after space trimming. You may consider those rules as the SQL constraints you may be used to. But since they would be defined at DDD level, they would apply on any database back-end, even if it does not support any constraint - e.g. if it is a NoSQL engine, or a third-party persistence service you do not have the hand on.























Then we define the needed methods of {\f1\fs20 IDomUserCommand} and {\f1\fs20 IDomUserQuery} in our custom class:
!type
!  TInfraRepoUser = class(TDDDRepositoryRestCommand,IDomUserCommand)
!  public
!    function SelectByLogonName(const aLogonName: RawUTF8): TCQRSResult;
!    function SelectByEmailValidation(aValidationState: TDomUserEmailValidation): TCQRSResult;
!    function SelectByLastName(const aName: TLastName; aStartWith: boolean): TCQRSResult;
!    function Get(out aAggregate: TUser): TCQRSResult;
!    function GetAll(out aAggregates: TUserObjArray): TCQRSResult;
!    function GetNext(out aAggregate: TUser): TCQRSResult;
!    function Add(const aAggregate: TUser): TCQRSResult;
!    function Update(const aUpdatedAggregate: TUser): TCQRSResult;
!    function HowManyValidatedEmail: integer;
!  end;

As you can see, some methods appear to me missing. There is no {\f1\fs20 Commit}, nor {\f1\fs20 Delete} - which are required by {\f1\fs20 IDomUserCommand}. But in fact, those commands are so generic that they are already implemented for you in {\f1\fs20 TDDDRepositoryRestCommand}!
What we need know is to implement those methods, using the internal protected {\f1\fs20 ORM*()} methods inherited by this parent class:
!function TInfraRepoUser.SelectByLogonName(const aLogonName: RawUTF8): TCQRSResult;
!begin
!  result := ORMSelectOne('LogonName=?',[aLogonName],(aLogonName=''));
!end;
!
................................................................................
!end;
Almost everything is already defined at {\f1\fs20 TDDDRepositoryRestCommand} level. Our {\f1\fs20 TInfraRepoUser} class, implementing a full CQRS service, fully abstracted from the ORM, is implemented by a few internal {\f1\fs20 ORM*()} method calls.
All the error handling, including server-side {\f1\fs20 exception} catching, and conversion into {\f1\fs20 TCQRSResult} / {\f1\fs20 ICQRSService.GetLastErrorInfo} content, is already implemented in {\f1\fs20 TDDDRepositoryRestCommand}.
All the data access via the {\f1\fs20 TSQLRecordUser} REST persistence layer, with any @56@ defined rule, is also incorporated in {\f1\fs20 TDDDRepositoryRestCommand}. The conversion to/from {\f1\fs20 TUser} properties has been optimized, so that fields would be moved {\i by reference}, with no memory allocation nor content modification, for best performance and data safety. The type mapping specified by {\f1\fs20 TInfraRepoUserFactory.Create} is enough to make the whole process as automated as possible.
In fact, our {\f1\fs20 TInfraRepoUser} {\f1\fs20 class} is just a thin wrapper forcing use of strong typing in its methods parameters (i.e. using {\f1\fs20 TUser}/{\f1\fs20 TUserObjArray} whereas the {\f1\fs20 ORM*()} methods are more relaxed about actual typing), and ensuring that the ORM specificities are followed as expected, e.g. a search against the {\f1\fs20 TUser.Name.Last} DDD field would use the {\f1\fs20 TSQLRecordUser.Name_Last} ORM column, with the proper {\f1\fs20 LIKE} operator.
Internally, {\f1\fs20 TDDDRepositoryRestCommand.ORMPrepareForCommit} will call all DDD and ORM {\f1\fs20 TSynFilter} and {\f1\fs20 TSynValidate} rules, as previously defined. It sounds indeed like a real advantage not to wait until the database layer is reached, to have those constraints verified. The sooner an error is notified, the better - especially in a complex @*SOA@ system.
Under the hood, {\f1\fs20 TDDDRepositoryRestCommand} will define a {\f1\fs20 TSqlRestBatch} - see @28@ - for storing all write commands in memory (as JSON) - e.g. {\f1\fs20 cmd.Add} - and will send them to the database engine, with optimized SQL or NoSQL statements, only when {\f1\fs20 cmd.Commit} would be executed.
:   Isolate using DTOs
DDD's {\i @*DTO@} may also be defined as {\f1\fs20 record}, and directly serialized as JSON via text-based serialization. Don't be afraid of writing some translation layers between {\f1\fs20 TSQLRecord} and DTO records or, more generally, between your {\i Application layer} and your {\i Presentation layer}. It will be very fast, on the server side. If your service interfaces are cleaner, do not hesitate.
But defining {\i DTO} types, just for uncoupling, may become time consuming. If you start writing a lot of wrapping code, forget about it, and expose your Domain {\i Value Objects} or even your {\i Entities}, as stated above. Or automate the wrapper coding, using RTTI and code generators. You have to weight the PROs and the CONs, like always... And never forget to write proper unit testing of this marshalling code, since it may induce some unexpected issues.
If you expect your DDD's objects to be {\i schema-less} or with an evolving structure (e.g. for {\i DTO}), depending on each context, you may benefit of not using a fixed {\f1\fs20 type} like {\f1\fs20 class} or {\f1\fs20 record}, but use @80@. This kind of {\f1\fs20 variant} will be serialized as JSON, and allow @*late-binding@ access to its properties (for {\i object} documents) or items (for {\i array} documents). In the context of interface-based services, using {\i per-reference} option at creation (i.e. {\f1\fs20 _ObjFast() _ArrFast() _JsonFast() _JsonFmtFast()} functions) does make sense, in order to spare the server resources.
:  Defining services
In practice, {\i mORMot}'s Client-Server architecture may be used as such:
- {\i @*Service@s via methods} - see @49@ - can be used to publish methods corresponding to your aggregate roots defined as {\f1\fs20 TSQLRecord}.\line This will make it pretty @*REST@ful compatible.
- {\i Services via interfaces} - see @63@ - can be used to publish all your processes.\line Dedicated factories can be used on both Client and Server side, to define your repositories and/or domain operations.






|
>







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|











>







 







|







13384
13385
13386
13387
13388
13389
13390
13391
13392
13393
13394
13395
13396
13397
13398
13399
.....
13432
13433
13434
13435
13436
13437
13438
13439
13440
13441
13442
13443
13444
13445
13446
.....
13458
13459
13460
13461
13462
13463
13464
13465
13466
13467
13468
13469
13470
13471
13472
13473
13474
13475
13476
13477
13478
13479
13480
13481
13482
13483
13484
13485
13486
13487
13488
13489
13490
13491
13492
13493
13494
13495
13496
13497
13498
13499
13500
13501
13502
13503
13504
13505
13506
13507
13508
13509
.....
13551
13552
13553
13554
13555
13556
13557
13558
13559
13560
13561
13562
13563
13564
13565
!        raise EMyApplicationException.CreateFmt('Commit error: %s',[cmd.GetLastErrorInfo]);
!    // here everything has been written to the database
!  finally
!    user.Free;
!  end;
This {\i dual-phase} commit appears to be a clean way of implement the @100@. Under the hood, when used with our ORM - as we will now explain - {\i @*Unit Of Work@} will be expressed as a {\f1\fs20 I*Command} service, uncoupled from the persistence layer it runs on.
:    Automated Repository using the ORM
As you may have noticed, we did just defined the {\f1\fs20 interface} types we needed. That is, we have the {\i contract} of our persistence services, but no actual implementation of it. As such, those {\f1\fs20 interface} definitions are useless. Luckily for use, the {\f1\fs20 mORMotDDD.pas} unit offers an easy way to implement those using @3@, with minimal coding.
:     DDD / ORM mapping
First we would need to map our domain object (i.e. our {\f1\fs20 TUser} instance and its properties) into a {\f1\fs20 TSQLRecord}. We may do it by hand, but you may find an handy way. Just run the following in the context of your application:
! TDDDRepositoryRestFactory.ComputeSQLRecord(TUser);
This {\f1\fs20 class procedure} would create a {\f1\fs20 ddsqlrecord.inc} file in the executable folder, containing the needed field definition, with one {\f1\fs20 TSQLRecord} type corresponding to each hierarchy level of the original {\f1\fs20 TPersistent} definition. Nested fields would be defined as a single column in the {\f1\fs20 TSQLRecord}, e.g. {\f1\fs20 Address.Country.Iso} would be flattened as a {\f1\fs20 Address_Country} property.
So if we follow the class hierarchy, we would have:
\graph DDDCQRSORMMapping CQRS Class Hierarchy Mapping for ORM and DDD Entities
\TPerson\TPersonContactable
\TPersonContactable\TUser
................................................................................
!    property LogonName: RawUTF8 read fLogonName write fLogonName;
!    property EmailValidated: TDomUserEmailValidation read fEmailValidated write fEmailValidated;
!  end;
In practice, the following property would need to be tuned as such:
!    property LogonName: RawUTF8 read fLogonName write fLogonName
!!      stored AS_UNIQUE;
Take a look at the {\f1\fs20 dddInfraRepoUser.pas} and {\f1\fs20 dddDomUserTypes.pas} units to make a comparison between the DDD objects and their corresponding {\f1\fs20 TSQLRecord*} types.
:     Define the Factory
Since the generated {\f1\fs20 TSQLRecordUser} type follows known conventions, the {\f1\fs20 mORMotDDD.pas} unit is able to do almost all the persistence work in an automated way, by inheriting of two classes:
- Defining a {\i Repository Factory} (i.e. a class able to generate {\f1\fs20 IDomUserQuery} or {\f1\fs20 IDomUserCommand} instances on requests) by inheriting from {\f1\fs20 TDDDRepositoryRestFactory}
- Defining the actual {\f1\fs20 IDomUserCommand} methods by inheriting from {\f1\fs20 TDDDRepositoryRestCommand}, and using high level protected methods to access the {\f1\fs20 TUser} from internal {\f1\fs20 TSQLRecordUser} ORM values.
First of all, we define the Factory:
!type
!  TInfraRepoUserFactory = class(TDDDRepositoryRestFactory)
!  public
................................................................................
- We would like to implement a {\f1\fs20 IDomUserCommand} contract - and, by the way, implement also its parent {\f1\fs20 IDomUserQuery interface};
- The actual implementation class would be {\f1\fs20 TInfraRepoUser} - which would be defined just after;
- The {\i Aggregate/Entity} class is a {\f1\fs20 TUser} kind of object;
- The associated {\f1\fs20 TSQLRest} server would be the one supplied to this class;
- The ORM class, defining the actual SQL table or NoSQL collection which would store the data, is {\f1\fs20 TSQLRecordUser};
- An optional {\f1\fs20 TDDDRepositoryRestManager} instance may be supplied as owner of this factory - but it is not used in most cases.
The {\f1\fs20 AddFilterOrValidate()} method allows to set some @56@ expectations at DDD level. Those rules would be applied before {\f1\fs20 Commit} would take place, without any use of the ORM rules. In the above code, {\f1\fs20 TSynFilterTrim} would remove any space from all text fields of the {\f1\fs20 TUser} instance, and {\f1\fs20 TSynValidateNonVoidText} will ensure that the {\f1\fs20 TUser.LogonName} field would not be {\f1\fs20 ''} - after space trimming. You may consider those rules as the SQL constraints you may be used to. But since they would be defined at DDD level, they would apply on any database back-end, even if it does not support any constraint - e.g. if it is a NoSQL engine, or a third-party persistence service you do not have the hand on.
You would probably want to use those CQRS interfaces, via usual @*IoC@, at {\f1\fs20 TSQLRest} level, just like any @63@:
!var cmd: IDomUserCommand;
!...
!  aServer.Services.Resolve(IDomUserCommand,cmd);
or, for a {\i Query}:
!var qry: IDomUserQuery;
!...
!  aServer.Services.Resolve(IDomUserQuery,qry);
In order to be able to get a {\f1\fs20 IDomUserCommand} or {\f1\fs20 IDomUserQuery} instance from {\f1\fs20 aServer.Services.Resolve()}, you would need to register the {\f1\fs20 TInfraRepoUserFactory} first:
! aServer.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aServer)],true);
or if you want to maintain the factory instance life-time (e.g. to share it with other interface resolvers):
!var factory: TInfraRepoUserFactory;
! ...
!  factory := TInfraRepoUserFactory.Create(aServer);
!  try
!    aServer.ServiceContainer.InjectResolver([factory]);
!  ...
!  finally
!    factory.Free;
!  end;
This single {\f1\fs20 TInfraRepoUserFactory} would allow to implement both {\f1\fs20 IDomUserCommand} and {\f1\fs20 IDomUserQuery} contracts.
Of course, having the ability to let {\f1\fs20 aServer} own the factory, via the {\f1\fs20 InjectResolver([...],true)} parameter, sounds easier to work with.
:     Implement the CQRS methods
Then we define the needed methods of {\f1\fs20 IDomUserCommand} and {\f1\fs20 IDomUserQuery} in our custom class:
!type
!  TInfraRepoUser = class(TDDDRepositoryRestCommand,IDomUserCommand,IDomUserQuery)
!  public
!    function SelectByLogonName(const aLogonName: RawUTF8): TCQRSResult;
!    function SelectByEmailValidation(aValidationState: TDomUserEmailValidation): TCQRSResult;
!    function SelectByLastName(const aName: TLastName; aStartWith: boolean): TCQRSResult;
!    function Get(out aAggregate: TUser): TCQRSResult;
!    function GetAll(out aAggregates: TUserObjArray): TCQRSResult;
!    function GetNext(out aAggregate: TUser): TCQRSResult;
!    function Add(const aAggregate: TUser): TCQRSResult;
!    function Update(const aUpdatedAggregate: TUser): TCQRSResult;
!    function HowManyValidatedEmail: integer;
!  end;
Note that we defined the {\f1\fs20 TInfraRepoUser} class as implementing both interface we need, via {\f1\fs20 = class(...,IDomUserCommand,IDomUserQuery}). We need both types to be explicit in the {\f1\fs20 class} type definition, otherwise, @*IoC@ - i.e. {\f1\fs20 aServer.Services.Resolve()} calls - won't work for both.
As you can see, some methods appear to me missing. There is no {\f1\fs20 Commit}, nor {\f1\fs20 Delete} - which are required by {\f1\fs20 IDomUserCommand}. But in fact, those commands are so generic that they are already implemented for you in {\f1\fs20 TDDDRepositoryRestCommand}!
What we need know is to implement those methods, using the internal protected {\f1\fs20 ORM*()} methods inherited by this parent class:
!function TInfraRepoUser.SelectByLogonName(const aLogonName: RawUTF8): TCQRSResult;
!begin
!  result := ORMSelectOne('LogonName=?',[aLogonName],(aLogonName=''));
!end;
!
................................................................................
!end;
Almost everything is already defined at {\f1\fs20 TDDDRepositoryRestCommand} level. Our {\f1\fs20 TInfraRepoUser} class, implementing a full CQRS service, fully abstracted from the ORM, is implemented by a few internal {\f1\fs20 ORM*()} method calls.
All the error handling, including server-side {\f1\fs20 exception} catching, and conversion into {\f1\fs20 TCQRSResult} / {\f1\fs20 ICQRSService.GetLastErrorInfo} content, is already implemented in {\f1\fs20 TDDDRepositoryRestCommand}.
All the data access via the {\f1\fs20 TSQLRecordUser} REST persistence layer, with any @56@ defined rule, is also incorporated in {\f1\fs20 TDDDRepositoryRestCommand}. The conversion to/from {\f1\fs20 TUser} properties has been optimized, so that fields would be moved {\i by reference}, with no memory allocation nor content modification, for best performance and data safety. The type mapping specified by {\f1\fs20 TInfraRepoUserFactory.Create} is enough to make the whole process as automated as possible.
In fact, our {\f1\fs20 TInfraRepoUser} {\f1\fs20 class} is just a thin wrapper forcing use of strong typing in its methods parameters (i.e. using {\f1\fs20 TUser}/{\f1\fs20 TUserObjArray} whereas the {\f1\fs20 ORM*()} methods are more relaxed about actual typing), and ensuring that the ORM specificities are followed as expected, e.g. a search against the {\f1\fs20 TUser.Name.Last} DDD field would use the {\f1\fs20 TSQLRecordUser.Name_Last} ORM column, with the proper {\f1\fs20 LIKE} operator.
Internally, {\f1\fs20 TDDDRepositoryRestCommand.ORMPrepareForCommit} will call all DDD and ORM {\f1\fs20 TSynFilter} and {\f1\fs20 TSynValidate} rules, as previously defined. It sounds indeed like a real advantage not to wait until the database layer is reached, to have those constraints verified. The sooner an error is notified, the better - especially in a complex @*SOA@ system.
Under the hood, {\f1\fs20 TDDDRepositoryRestCommand} will define a {\f1\fs20 TSqlRestBatch} - see @28@ - for storing all write commands in memory (as JSON) - e.g. {\f1\fs20 cmd.Add} - and will send them to the database engine, with optimized SQL or NoSQL statements, only when {\f1\fs20 cmd.Commit} would be executed.
:   Isolate your Domain using DTOs
DDD's {\i @*DTO@} may also be defined as {\f1\fs20 record}, and directly serialized as JSON via text-based serialization. Don't be afraid of writing some translation layers between {\f1\fs20 TSQLRecord} and DTO records or, more generally, between your {\i Application layer} and your {\i Presentation layer}. It will be very fast, on the server side. If your service interfaces are cleaner, do not hesitate.
But defining {\i DTO} types, just for uncoupling, may become time consuming. If you start writing a lot of wrapping code, forget about it, and expose your Domain {\i Value Objects} or even your {\i Entities}, as stated above. Or automate the wrapper coding, using RTTI and code generators. You have to weight the PROs and the CONs, like always... And never forget to write proper unit testing of this marshalling code, since it may induce some unexpected issues.
If you expect your DDD's objects to be {\i schema-less} or with an evolving structure (e.g. for {\i DTO}), depending on each context, you may benefit of not using a fixed {\f1\fs20 type} like {\f1\fs20 class} or {\f1\fs20 record}, but use @80@. This kind of {\f1\fs20 variant} will be serialized as JSON, and allow @*late-binding@ access to its properties (for {\i object} documents) or items (for {\i array} documents). In the context of interface-based services, using {\i per-reference} option at creation (i.e. {\f1\fs20 _ObjFast() _ArrFast() _JsonFast() _JsonFmtFast()} functions) does make sense, in order to spare the server resources.
:  Defining services
In practice, {\i mORMot}'s Client-Server architecture may be used as such:
- {\i @*Service@s via methods} - see @49@ - can be used to publish methods corresponding to your aggregate roots defined as {\f1\fs20 TSQLRecord}.\line This will make it pretty @*REST@ful compatible.
- {\i Services via interfaces} - see @63@ - can be used to publish all your processes.\line Dedicated factories can be used on both Client and Server side, to define your repositories and/or domain operations.

Changes to SQLite3/mORMot.pas.

2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301











2302
2303
2304
2305
2306
2307
2308
....
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
....
9128
9129
9130
9131
9132
9133
9134


9135
9136
9137
9138
9139
9140
9141
....
9165
9166
9167
9168
9169
9170
9171
9172
9173
9174
9175
9176
9177
9178
9179
....
9203
9204
9205
9206
9207
9208
9209


9210

9211
9212
9213
9214
9215
9216
9217
.....
12328
12329
12330
12331
12332
12333
12334











12335
12336
12337
12338
12339
12340
12341
.....
14340
14341
14342
14343
14344
14345
14346










14347
14348
14349
14350
14351
14352
14353
.....
14592
14593
14594
14595
14596
14597
14598




14599
14600
14601
14602
14603
14604
14605
.....
15240
15241
15242
15243
15244
15245
15246









15247
15248
15249
15250
15251
15252
15253
.....
25155
25156
25157
25158
25159
25160
25161












































25162
25163
25164
25165
25166
25167
25168
.....
31205
31206
31207
31208
31209
31210
31211
31212
31213
31214
31215
31216
31217
31218
31219
31220
31221
31222
31223
31224
31225
31226
31227
31228
31229
31230
31231
31232
31233
31234
31235
31236
.....
32423
32424
32425
32426
32427
32428
32429







32430
32431
32432
32433
32434
32435
32436
32437
32438
32439
32440
32441
32442
32443
32444
32445
32446
32447
32448
32449
.....
34414
34415
34416
34417
34418
34419
34420
34421
34422
34423
34424
34425
34426
34427
34428
34429
34430
34431
34432
34433
34434
34435
34436
34437
34438
34439
34440
34441
34442
34443
34444
34445
34446
34447
34448
34449
34450
34451
34452
34453
34454
34455
34456
34457
34458
.....
38173
38174
38175
38176
38177
38178
38179





38180
38181
38182
38183
38184
38185
38186
.....
38852
38853
38854
38855
38856
38857
38858







38859
38860
38861
38862
38863
38864
38865
.....
46176
46177
46178
46179
46180
46181
46182


46183
46184
46185
46186
46187
46188
46189
.....
46196
46197
46198
46199
46200
46201
46202



46203
46204
46205
46206
46207

46208
46209









46210
46211

46212
46213
46214
46215
46216
46217
46218
.....
46360
46361
46362
46363
46364
46365
46366
46367

46368
46369
46370
46371
46372
46373
46374
46375
46376


46377
46378
46379
46380
46381
46382
46383
.....
46389
46390
46391
46392
46393
46394
46395
46396
46397
46398
46399
46400
46401
46402
46403
    // - will return the exact code page since Delphi 2009, from RTTI
    // - for non Unicode versions of Delphi, will recognize WinAnsiString as
    // CODEPAGE_US, RawUnicode as CP_UTF16, RawByteString as CP_RAWBYTESTRING,
    // AnsiString as 0, and any other type as RawUTF8
    function AnsiStringCodePage: integer; {$ifdef UNICODE}inline;{$endif}
    /// get the TGUID of a given interface type information
    // - returns nil if this type is not an interface
    function InterfaceGUID: PGUID;
    /// get the unit name of a given interface type information
    // - returns '' if this type is not an interface
    function InterfaceUnitName: PShortString;











  end;

{$ifdef FPC}
{$PACKRECORDS 1}
{$else}
{$A-}
{$endif}
................................................................................
    /// this overriden constructor will release all its nested
    // TPersistent class published properties
    destructor Destroy; override;
  end;

  {$endif LVCL}

  /// class-reference type (metaclass) of a TInterfacedObject kind
  TInterfacedObjectClass = class of TInterfacedObject;

  /// abstract TPersistent class, which will instantiate all its nested TPersistent
  // class published properties, then release them (and any T*ObjArray) when freed
  // - TSynAutoCreateFields is to be preferred in most cases, due to its lower overhead
  // - note that non published (e.g. public) properties won't be instantiated
  // - please take care that you would not create any endless recursion: you
  // should ensure that at one level, nested published properties won't have any
  // class instance matching its parent type
................................................................................
  end;
  {$M-}

  /// abstract factory class targetting a single kind of interface
  TInterfaceResolverForSingleInterface = class(TInterfaceResolver)
  protected
    fInterfaceTypeInfo: PTypeInfo;


    fImplementationEntry: PInterfaceEntry;
    fImplementationClass: TInterfacedObjectClass;
    fImplementationClassIsCustomCreate: boolean;
    function TryResolve(aInterface: PTypeInfo; out Obj): boolean; override;
    function CreateInstance: TInterfacedObject; virtual;
  public
    /// this overriden constructor will check and store the supplied class
................................................................................
  // and doing the instance resolution using the overloaded Resolve*() methods
  // - TServiceContainer will inherit from this class, as the main entry point
  // for interface-based services of the framework (via TSQLRest.Services)
  // - you can use RegisterGlobal() class method to define some process-wide DI
  TInterfaceResolverInjected = class(TInterfaceResolver)
  protected
    fResolvers: TInterfaceResolverObjArray;
    fCreatedInterfaceStub: TInterfaceStubObjArray;
    fDependencies: TInterfacedObjectObjArray;
    function TryResolve(aInterface: PTypeInfo; out Obj): boolean; override;
    class function RegisterGlobalCheck(aInterface: PTypeInfo;
      aImplementationClass: TClass): PInterfaceEntry;
  public
    /// define a global class type for interface resolution
    // - most of the time, you would need a local DI/IoC resolution list; but
................................................................................
    /// prepare and setup interface DI/IoC resolution with some blank
    // TInterfaceStub specified by their TGUID
    procedure InjectStub(const aStubsByGUID: array of TGUID); overload; virtual;
    /// prepare and setup interface DI/IoC resolution with TInterfaceResolver
    // kind of factory
    // - e.g. a customized TInterfaceStub/TInterfaceMock, a TServiceContainer,
    // a TDDDRepositoryRestObjectMapping or any factory class


    procedure InjectResolver(const aOtherResolvers: array of TInterfaceResolver); overload; virtual;

    /// prepare and setup interface DI/IoC resolution from a TInterfacedObject instance
    // - any TInterfacedObject declared as dependency will have its reference
    // count increased, and decreased in Destroy
    procedure InjectInstance(const aDependencies: array of TInterfacedObject); overload; virtual;
    /// can be used to perform an DI/IoC for a given interface
    // - will search for the supplied interface to its internal list of resolvers
    // - returns TRUE and set the Obj variable with a matching instance
................................................................................
    // ! if fServer.Services['Calculator'].Get(Calc)) then
    // !   ...
    // - safer typical use, following the DI/IoC pattern, and which would not
    // trigger any access violation if Services=nil, could be:
    // ! if fServer.Services.Resolve(ICalculator,Calc) then
    // !   ...
    property Services: TServiceContainer read fServices;











    /// the routing classs of the service remote request
    // - by default, will use TSQLRestRoutingREST, i.e. an URI-based
    // layout which is secure (since will use our RESTful authentication scheme),
    // and also very fast
    // - but TSQLRestRoutingJSON_RPC can e.g. be set (on BOTH client and
    // server sides), if the client would rather use JSON/RPC alternative pattern
    // - NEVER set the abstract TSQLRestServerURIContext class on this property
................................................................................
      const aInterfaces: array of TGUID; const aContractExpected: RawUTF8=''): TServiceFactoryServer; overload;
    /// register a remote Service via its interface
    // - this method expects the interface(s) to have been registered previously:
    // ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
    function ServiceDefine(aClient: TSQLRest; const aInterfaces: array of TGUID;
      aInstanceCreation: TServiceInstanceImplementation=sicSingle;
      const aContractExpected: RawUTF8=''): boolean; overload;











    /// compute the full statistics about this server, as JSON
    // - is a wrapper around the Stats() method-based service, setting withall=1
    function FullStatsAsJson: RawUTF8; virtual;
    /// compute the full statistics about this server, as a TDocVariant document
    // - is a wrapper around the Stats() method-based service, setting withall=1
    function FullStatsAsDocVariant: variant;
................................................................................
      out ResultID: TIDDynArray): boolean; overload; virtual;
    /// search for a field value, according to its SQL content representation
    // - return true on success (i.e. if some values have been added to ResultID)
    // - store the results into the ResultID dynamic array
    // - faster than OneFieldValues method, which creates a temporary JSON content
    function SearchField(const FieldName, FieldValue: RawUTF8;
      out ResultID: TIDDynArray): boolean; overload; virtual; abstract;





    /// read only access to a boolean value set to true if table data was modified
    property Modified: boolean read fModified write fModified;
    /// read only access to the class defining the record type stored in this
    // REST storage
    property StoredClass: TSQLRecordClass read fStoredClass;
    /// read only access to the ORM properties of the associated record type
................................................................................
    /// end a transaction (calls REST END Member)
    // - by default, Client transaction will use here a pseudo session
    procedure Commit(SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED;
      RaiseException: boolean=false); override;
    /// abort a transaction (calls REST ABORT Member)
    // - by default, Client transaction will use here a pseudo session
    procedure RollBack(SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED); override;










    /// if set to TRUE, all BLOB fields of all tables will be transferred
    // between the Client and the remote Server
    // - i.e. Add() Update() will use Blob-related RESTful PUT/POST request
    // - i.e. Retrieve() will use Blob-related RESTful GET request
    // - note that the Refresh method won't handle BLOB fields, even if this
    // property setting is set to TRUE
................................................................................
function TTypeInfo.InterfaceUnitName: PShortString;
begin
  if (@self=nil) or (Kind<>tkInterface) then
    result := @NULL_SHORTSTRING else
    result := @PInterfaceTypeData(AlignToPtr(@Name[ord(Name[0])+1]))^.IntfUnit;
end;














































{ TClassProp }

function TClassProp.FieldProp(const PropName: shortstring): PPropInfo;
var i: integer;
begin
  if @self<>nil then begin
................................................................................
function TSQLRestClientURI.ServiceRegister(const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): boolean;
begin
  result := False;
  if (self=nil) or (high(aInterfaces)<0) then
    exit;
  if fServices=nil then
    fServices := TServiceContainerClient.Create(self);
  result := (fServices as TServiceContainerClient).AddInterface(
    aInterfaces,aInstanceCreation,aContractExpected);
end;

function TSQLRestClientURI.ServiceRegister(aInterface: PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): TServiceFactory;
begin
  result := nil;
  if (self=nil) or (aInterface=nil) then begin
    SetLastException;
    exit;
  end;
  if fServices=nil then
    fServices := TServiceContainerClient.Create(self);
  with fServices as TServiceContainerClient do
  try
    result := AddInterface(aInterface,aInstanceCreation,aContractExpected);
  except
    on E: Exception do
      SetLastException(E);
  end;
end;
................................................................................
      ListUpdated.Free;
      ListDeleted.Free;
    end;
  finally
    fAcquireExecution[execORMWrite].Leave;
  end;
end;








function TSQLRestServer.RecordVersionSynchronizeSubscribeMaster(Table: TSQLRecordClass;
  RecordVersion: TRecordVersion; const SlaveCallback: IServiceRecordVersionCallback): boolean;
begin
  if self=nil then begin
    result := false;
    exit;
  end;
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  result := TServiceContainerServer(fServices).
    RecordVersionSynchronizeSubscribeMaster(Model.GetTableIndexExisting(Table),
      RecordVersion,SlaveCallback);
end;

function TSQLRestServer.RecordVersionSynchronizeMasterStart(
  ByPassAuthentication: boolean): boolean;
var factory: TServiceFactory;
begin
  if Services<>nil then begin
................................................................................
end;

function TSQLRestServer.ServiceRegister(
  aImplementationClass: TInterfacedClass; const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): TServiceFactoryServer;
begin
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  if (aImplementationClass=nil) or (high(aInterfaces)<0) then
    result := nil else
    result := (fServices as TServiceContainerServer).
      AddImplementation(aImplementationClass,aInterfaces,aInstanceCreation,nil,aContractExpected);
end;

function TSQLRestServer.ServiceRegister(aSharedImplementation: TInterfacedObject;
  const aInterfaces: array of PTypeInfo; const aContractExpected: RawUTF8): TServiceFactoryServer;
begin
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  if (self=nil) or (aSharedImplementation=nil) or (high(aInterfaces)<0) then
    result := nil else
    result := (fServices as TServiceContainerServer).
      AddImplementation(TInterfacedClass(aSharedImplementation.ClassType),
        aInterfaces,sicShared,aSharedImplementation,aContractExpected);
end;

function TSQLRestServer.ServiceRegister(aClient: TSQLRest;
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): boolean;
begin
  result := False;
  if (self=nil) or (high(aInterfaces)<0) or (aClient=nil) then
    exit;
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  result := (fServices as TServiceContainerServer).AddInterface(
    aInterfaces,aInstanceCreation,aContractExpected);
end;

function TSQLRestServer.ServiceDefine(aImplementationClass: TInterfacedClass;
  const aInterfaces: array of TGUID; aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): TServiceFactoryServer;
begin
................................................................................
  // nothing to do in this basic REST static class
end;

procedure TSQLRestStorage.EndCurrentThread(Sender: TThread);
begin // called by TSQLRestServer.EndCurrentThread
  // nothing to do in this basic REST static class
end;






function TSQLRestStorage.CreateSQLMultiIndex(Table: TSQLRecordClass;
  const FieldNames: array of RawUTF8; Unique: boolean; IndexName: RawUTF8): boolean;
begin
  result := false; // not implemented in this basic REST static class
end;

................................................................................
      exit;
    Res.GetRowValues(0,TInt64DynArray(DataID));
    result := true;
  finally
    Res.Free;
  end;
end;









{ TSQLRecordLog }

destructor TSQLRecordLog.Destroy;
begin
  fLogTableWriter.Free;
................................................................................
  guid := aInterface^.InterfaceGUID;
  if guid=nil then
    raise EInterfaceResolverException.CreateUTF8('%.Create expects an Interface',[self]);
  fImplementationEntry := aImplementation.GetInterfaceEntry(guid^);
  if fImplementationEntry=nil then
    raise EInterfaceResolverException.CreateUTF8('%.Create: % does not implement %',
      [self,aImplementation,fInterfaceTypeInfo^.Name]);


  fImplementationClass := aImplementation;
  fImplementationClassIsCustomCreate :=
    aImplementation.InheritsFrom(TInterfacedObjectWithCustomCreate);
end;

constructor TInterfaceResolverForSingleInterface.Create(const aInterface: TGUID;
  aImplementation: TInterfacedObjectClass);
................................................................................
  if not fImplementationClassIsCustomCreate then
    result := fImplementationClass.Create else
    result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create;
end;

function TInterfaceResolverForSingleInterface.GetOneInstance(out Obj): boolean;
begin



  result := GetInterfaceFromEntry(CreateInstance,fImplementationEntry,Obj);
end;

function TInterfaceResolverForSingleInterface.TryResolve(
  aInterface: PTypeInfo; out Obj): boolean;

begin
  if fInterfaceTypeInfo<>aInterface then









    result := false else
    result := GetOneInstance(Obj);

end;


{ TInterfaceResolverInjected }

var
  GlobalInterfaceResolutionLock: TRTLCriticalSection;
................................................................................
var i: integer;
begin
  for i := 0 to high(aStubsByGUID) do
    InjectResolver([TInterfaceStub.Create(aStubsByGUID[i])]);
end;

procedure TInterfaceResolverInjected.InjectResolver(
  const aOtherResolvers: array of TInterfaceResolver);

var i: integer;
begin
  for i := 0 to high(aOtherResolvers) do
  if aOtherResolvers[i]<>nil then begin
    if aOtherResolvers[i].InheritsFrom(TInterfaceStub) then begin
      include(TInterfaceStub(aOtherResolvers[i]).fOptions,
        imoFakeInstanceWontReleaseTInterfaceStub);
      ObjArrayAdd(fCreatedInterfaceStub,aOtherResolvers[i]);
    end;


    ObjArrayAddOnce(fResolvers,aOtherResolvers[i]);
  end;
end;

procedure TInterfaceResolverInjected.InjectInstance(
  const aDependencies: array of TInterfacedObject);
var i: integer;
................................................................................
  end;
end;

destructor TInterfaceResolverInjected.Destroy;
var i: integer;
begin
  try
    ObjArrayClear(fCreatedInterfaceStub);
    for i := 0 to length(fDependencies)-1 do
      IInterface(fDependencies[i])._Release;
  finally
    inherited Destroy;
  end;
end;







|



>
>
>
>
>
>
>
>
>
>
>







 







<
<
<







 







>
>







 







|







 







>
>
|
>







 







>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>







 







>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
|












<
<
|







 







>
>
>
>
>
>
>




|
|
<
<
<
<
|
|
|







 







<
<


|






<
<


|












<
<
|







 







>
>
>
>
>







 







>
>
>
>
>
>
>







 







>
>







 







>
>
>
|




>

|
>
>
>
>
>
>
>
>
>
|
<
>







 







|
>







|
|
>
>







 







|







2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
....
3779
3780
3781
3782
3783
3784
3785



3786
3787
3788
3789
3790
3791
3792
....
9136
9137
9138
9139
9140
9141
9142
9143
9144
9145
9146
9147
9148
9149
9150
9151
....
9175
9176
9177
9178
9179
9180
9181
9182
9183
9184
9185
9186
9187
9188
9189
....
9213
9214
9215
9216
9217
9218
9219
9220
9221
9222
9223
9224
9225
9226
9227
9228
9229
9230
.....
12341
12342
12343
12344
12345
12346
12347
12348
12349
12350
12351
12352
12353
12354
12355
12356
12357
12358
12359
12360
12361
12362
12363
12364
12365
.....
14364
14365
14366
14367
14368
14369
14370
14371
14372
14373
14374
14375
14376
14377
14378
14379
14380
14381
14382
14383
14384
14385
14386
14387
.....
14626
14627
14628
14629
14630
14631
14632
14633
14634
14635
14636
14637
14638
14639
14640
14641
14642
14643
.....
15278
15279
15280
15281
15282
15283
15284
15285
15286
15287
15288
15289
15290
15291
15292
15293
15294
15295
15296
15297
15298
15299
15300
.....
25202
25203
25204
25205
25206
25207
25208
25209
25210
25211
25212
25213
25214
25215
25216
25217
25218
25219
25220
25221
25222
25223
25224
25225
25226
25227
25228
25229
25230
25231
25232
25233
25234
25235
25236
25237
25238
25239
25240
25241
25242
25243
25244
25245
25246
25247
25248
25249
25250
25251
25252
25253
25254
25255
25256
25257
25258
25259
.....
31296
31297
31298
31299
31300
31301
31302


31303
31304
31305
31306
31307
31308
31309
31310
31311
31312
31313
31314
31315


31316
31317
31318
31319
31320
31321
31322
31323
.....
32510
32511
32512
32513
32514
32515
32516
32517
32518
32519
32520
32521
32522
32523
32524
32525
32526
32527
32528
32529




32530
32531
32532
32533
32534
32535
32536
32537
32538
32539
.....
34504
34505
34506
34507
34508
34509
34510


34511
34512
34513
34514
34515
34516
34517
34518
34519


34520
34521
34522
34523
34524
34525
34526
34527
34528
34529
34530
34531
34532
34533
34534


34535
34536
34537
34538
34539
34540
34541
34542
.....
38257
38258
38259
38260
38261
38262
38263
38264
38265
38266
38267
38268
38269
38270
38271
38272
38273
38274
38275
.....
38941
38942
38943
38944
38945
38946
38947
38948
38949
38950
38951
38952
38953
38954
38955
38956
38957
38958
38959
38960
38961
.....
46272
46273
46274
46275
46276
46277
46278
46279
46280
46281
46282
46283
46284
46285
46286
46287
.....
46294
46295
46296
46297
46298
46299
46300
46301
46302
46303
46304
46305
46306
46307
46308
46309
46310
46311
46312
46313
46314
46315
46316
46317
46318
46319
46320
46321

46322
46323
46324
46325
46326
46327
46328
46329
.....
46471
46472
46473
46474
46475
46476
46477
46478
46479
46480
46481
46482
46483
46484
46485
46486
46487
46488
46489
46490
46491
46492
46493
46494
46495
46496
46497
.....
46503
46504
46505
46506
46507
46508
46509
46510
46511
46512
46513
46514
46515
46516
46517
    // - will return the exact code page since Delphi 2009, from RTTI
    // - for non Unicode versions of Delphi, will recognize WinAnsiString as
    // CODEPAGE_US, RawUnicode as CP_UTF16, RawByteString as CP_RAWBYTESTRING,
    // AnsiString as 0, and any other type as RawUTF8
    function AnsiStringCodePage: integer; {$ifdef UNICODE}inline;{$endif}
    /// get the TGUID of a given interface type information
    // - returns nil if this type is not an interface
    function InterfaceGUID: PGUID; {$ifdef UNICODE}inline;{$endif}
    /// get the unit name of a given interface type information
    // - returns '' if this type is not an interface
    function InterfaceUnitName: PShortString;
    /// get the ancestor/parent of a given interface type information
    // - returns nil if this type has no parent
    function InterfaceAncestor: PTypeInfo; {$ifdef UNICODE}inline;{$endif}
    /// get all ancestors/parents of a given interface type information
    // - only ancestors with an associated TGUID would be added
    // - if OnlyImplementedBy is not nil, only the interface explicitly
    // implemented by this class would be added, and AncestorsImplementedEntry[]
    // would contain the corresponding PInterfaceEntry values 
    procedure InterfaceAncestors(out Ancestors: PTypeInfoDynArray;
      OnlyImplementedBy: TInterfacedObjectClass;
      out AncestorsImplementedEntry: TPointerDynArray);
  end;

{$ifdef FPC}
{$PACKRECORDS 1}
{$else}
{$A-}
{$endif}
................................................................................
    /// this overriden constructor will release all its nested
    // TPersistent class published properties
    destructor Destroy; override;
  end;

  {$endif LVCL}




  /// abstract TPersistent class, which will instantiate all its nested TPersistent
  // class published properties, then release them (and any T*ObjArray) when freed
  // - TSynAutoCreateFields is to be preferred in most cases, due to its lower overhead
  // - note that non published (e.g. public) properties won't be instantiated
  // - please take care that you would not create any endless recursion: you
  // should ensure that at one level, nested published properties won't have any
  // class instance matching its parent type
................................................................................
  end;
  {$M-}

  /// abstract factory class targetting a single kind of interface
  TInterfaceResolverForSingleInterface = class(TInterfaceResolver)
  protected
    fInterfaceTypeInfo: PTypeInfo;
    fInterfaceAncestors: PTypeInfoDynArray;
    fInterfaceAncestorsImplementationEntry: TPointerDynArray;
    fImplementationEntry: PInterfaceEntry;
    fImplementationClass: TInterfacedObjectClass;
    fImplementationClassIsCustomCreate: boolean;
    function TryResolve(aInterface: PTypeInfo; out Obj): boolean; override;
    function CreateInstance: TInterfacedObject; virtual;
  public
    /// this overriden constructor will check and store the supplied class
................................................................................
  // and doing the instance resolution using the overloaded Resolve*() methods
  // - TServiceContainer will inherit from this class, as the main entry point
  // for interface-based services of the framework (via TSQLRest.Services)
  // - you can use RegisterGlobal() class method to define some process-wide DI
  TInterfaceResolverInjected = class(TInterfaceResolver)
  protected
    fResolvers: TInterfaceResolverObjArray;
    fResolversToBeReleased: TInterfaceResolverObjArray;
    fDependencies: TInterfacedObjectObjArray;
    function TryResolve(aInterface: PTypeInfo; out Obj): boolean; override;
    class function RegisterGlobalCheck(aInterface: PTypeInfo;
      aImplementationClass: TClass): PInterfaceEntry;
  public
    /// define a global class type for interface resolution
    // - most of the time, you would need a local DI/IoC resolution list; but
................................................................................
    /// prepare and setup interface DI/IoC resolution with some blank
    // TInterfaceStub specified by their TGUID
    procedure InjectStub(const aStubsByGUID: array of TGUID); overload; virtual;
    /// prepare and setup interface DI/IoC resolution with TInterfaceResolver
    // kind of factory
    // - e.g. a customized TInterfaceStub/TInterfaceMock, a TServiceContainer,
    // a TDDDRepositoryRestObjectMapping or any factory class
    // - by default, only TInterfaceStub/TInterfaceMock would be owned by this
    // instance, and released by Destroy - unless you set OwnOtherResolvers
    procedure InjectResolver(const aOtherResolvers: array of TInterfaceResolver;
      OwnOtherResolvers: boolean=false); overload; virtual;
    /// prepare and setup interface DI/IoC resolution from a TInterfacedObject instance
    // - any TInterfacedObject declared as dependency will have its reference
    // count increased, and decreased in Destroy
    procedure InjectInstance(const aDependencies: array of TInterfacedObject); overload; virtual;
    /// can be used to perform an DI/IoC for a given interface
    // - will search for the supplied interface to its internal list of resolvers
    // - returns TRUE and set the Obj variable with a matching instance
................................................................................
    // ! if fServer.Services['Calculator'].Get(Calc)) then
    // !   ...
    // - safer typical use, following the DI/IoC pattern, and which would not
    // trigger any access violation if Services=nil, could be:
    // ! if fServer.Services.Resolve(ICalculator,Calc) then
    // !   ...
    property Services: TServiceContainer read fServices;
    /// access or initialize the internal IoC resolver, used for interface-based
    // remote services, and more generaly any Services.Resolve() call
    // - create and initialize the internal TServiceContainer if no service
    // interface has been registered yet
    // - may be used to inject some dependencies, which are not interface-based
    // remote services, but internal IoC, without the ServiceRegister()
    // or ServiceDefine() methods - e.g.
    // ! aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true);
    // - overriden methods would return TServiceContainerClient or
    // TServiceContainerServer instances, on TSQLRestClient or TSQLRestServer
    function ServiceContainer: TServiceContainer; virtual; abstract;
    /// the routing classs of the service remote request
    // - by default, will use TSQLRestRoutingREST, i.e. an URI-based
    // layout which is secure (since will use our RESTful authentication scheme),
    // and also very fast
    // - but TSQLRestRoutingJSON_RPC can e.g. be set (on BOTH client and
    // server sides), if the client would rather use JSON/RPC alternative pattern
    // - NEVER set the abstract TSQLRestServerURIContext class on this property
................................................................................
      const aInterfaces: array of TGUID; const aContractExpected: RawUTF8=''): TServiceFactoryServer; overload;
    /// register a remote Service via its interface
    // - this method expects the interface(s) to have been registered previously:
    // ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
    function ServiceDefine(aClient: TSQLRest; const aInterfaces: array of TGUID;
      aInstanceCreation: TServiceInstanceImplementation=sicSingle;
      const aContractExpected: RawUTF8=''): boolean; overload;
    /// access or initialize the internal IoC resolver, used for interface-based
    // remote services, and more generaly any Services.Resolve() call
    // - create and initialize the internal TServiceContainerServer if no
    // service interface has been registered yet
    // - may be used to inject some dependencies, which are not interface-based
    // remote services, but internal IoC, without the ServiceRegister()
    // or ServiceDefine() methods - e.g.
    // ! aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true);
    // - this overriden method would return a TServiceContainerServer instance
    function ServiceContainer: TServiceContainer; override;

    /// compute the full statistics about this server, as JSON
    // - is a wrapper around the Stats() method-based service, setting withall=1
    function FullStatsAsJson: RawUTF8; virtual;
    /// compute the full statistics about this server, as a TDocVariant document
    // - is a wrapper around the Stats() method-based service, setting withall=1
    function FullStatsAsDocVariant: variant;
................................................................................
      out ResultID: TIDDynArray): boolean; overload; virtual;
    /// search for a field value, according to its SQL content representation
    // - return true on success (i.e. if some values have been added to ResultID)
    // - store the results into the ResultID dynamic array
    // - faster than OneFieldValues method, which creates a temporary JSON content
    function SearchField(const FieldName, FieldValue: RawUTF8;
      out ResultID: TIDDynArray): boolean; overload; virtual; abstract;
    /// access or initialize the internal IoC resolver
    // - this overriden method would return always nil, since IoC only makes
    // sense at TSQLRestClient and TSQLRestServer level
    function ServiceContainer: TServiceContainer; override;

    /// read only access to a boolean value set to true if table data was modified
    property Modified: boolean read fModified write fModified;
    /// read only access to the class defining the record type stored in this
    // REST storage
    property StoredClass: TSQLRecordClass read fStoredClass;
    /// read only access to the ORM properties of the associated record type
................................................................................
    /// end a transaction (calls REST END Member)
    // - by default, Client transaction will use here a pseudo session
    procedure Commit(SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED;
      RaiseException: boolean=false); override;
    /// abort a transaction (calls REST ABORT Member)
    // - by default, Client transaction will use here a pseudo session
    procedure RollBack(SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED); override;
    /// access or initialize the internal IoC resolver, used for interface-based
    // remote services, and more generaly any Services.Resolve() call
    // - create and initialize the internal TServiceContainerClient if no
    // service interface has been registered yet
    // - may be used to inject some dependencies, which are not interface-based
    // remote services, but internal IoC, without the ServiceRegister()
    // or ServiceDefine() methods - e.g.
    // ! aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true);
    function ServiceContainer: TServiceContainer; override;

    /// if set to TRUE, all BLOB fields of all tables will be transferred
    // between the Client and the remote Server
    // - i.e. Add() Update() will use Blob-related RESTful PUT/POST request
    // - i.e. Retrieve() will use Blob-related RESTful GET request
    // - note that the Refresh method won't handle BLOB fields, even if this
    // property setting is set to TRUE
................................................................................
function TTypeInfo.InterfaceUnitName: PShortString;
begin
  if (@self=nil) or (Kind<>tkInterface) then
    result := @NULL_SHORTSTRING else
    result := @PInterfaceTypeData(AlignToPtr(@Name[ord(Name[0])+1]))^.IntfUnit;
end;

function TTypeInfo.InterfaceAncestor: PTypeInfo;
begin
  if (@self=nil) or (Kind<>tkInterface) then
    result := nil else
    with PInterfaceTypeData(AlignToPtr(@Name[ord(Name[0])+1]))^ do
      if IntfParent=nil then
        result := nil else
        result := IntfParent{$ifndef FPC}^{$endif};
end;

procedure TTypeInfo.InterfaceAncestors(out Ancestors: PTypeInfoDynArray;
  OnlyImplementedBy: TInterfacedObjectClass;
  out AncestorsImplementedEntry: TPointerDynArray);
var n: integer;
    nfo: PTypeInfo;
    typ: PInterfaceTypeData;
    entry: pointer;
begin
  if (@self=nil) or (Kind<>tkInterface) then
    exit;
  n := 0;
  typ := AlignToPtr(@Name[ord(Name[0])+1]);
  repeat
    if typ^.IntfParent=nil then
      exit;
    nfo := typ^.IntfParent{$ifndef FPC}^{$endif};
    if nfo=TypeInfo(IInterface) then
      exit;
    typ := AlignToPtr(@nfo^.Name[ord(nfo^.Name[0])+1]);
    if ifHasGuid in typ^.IntfFlags then begin
      if OnlyImplementedBy<>nil then begin
        entry := OnlyImplementedBy.GetInterfaceEntry(typ^.IntfGuid);
        if entry=nil then
          continue;
        Setlength(AncestorsImplementedEntry,n+1);
        AncestorsImplementedEntry[n] := entry;
      end;
      SetLength(Ancestors,n+1);
      Ancestors[n] := nfo;
      inc(n);
    end;
  until false;
end;


{ TClassProp }

function TClassProp.FieldProp(const PropName: shortstring): PPropInfo;
var i: integer;
begin
  if @self<>nil then begin
................................................................................
function TSQLRestClientURI.ServiceRegister(const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): boolean;
begin
  result := False;
  if (self=nil) or (high(aInterfaces)<0) then
    exit;


  result := (ServiceContainer as TServiceContainerClient).AddInterface(
    aInterfaces,aInstanceCreation,aContractExpected);
end;

function TSQLRestClientURI.ServiceRegister(aInterface: PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): TServiceFactory;
begin
  result := nil;
  if (self=nil) or (aInterface=nil) then begin
    SetLastException;
    exit;
  end;


  with ServiceContainer as TServiceContainerClient do
  try
    result := AddInterface(aInterface,aInstanceCreation,aContractExpected);
  except
    on E: Exception do
      SetLastException(E);
  end;
end;
................................................................................
      ListUpdated.Free;
      ListDeleted.Free;
    end;
  finally
    fAcquireExecution[execORMWrite].Leave;
  end;
end;

function TSQLRestServer.ServiceContainer: TServiceContainer;
begin
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  result := fServices;
end;

function TSQLRestServer.RecordVersionSynchronizeSubscribeMaster(Table: TSQLRecordClass;
  RecordVersion: TRecordVersion; const SlaveCallback: IServiceRecordVersionCallback): boolean;
begin
  if self=nil then
    result := false else




    result := (ServiceContainer as TServiceContainerServer).
      RecordVersionSynchronizeSubscribeMaster(Model.GetTableIndexExisting(Table),
        RecordVersion,SlaveCallback);
end;

function TSQLRestServer.RecordVersionSynchronizeMasterStart(
  ByPassAuthentication: boolean): boolean;
var factory: TServiceFactory;
begin
  if Services<>nil then begin
................................................................................
end;

function TSQLRestServer.ServiceRegister(
  aImplementationClass: TInterfacedClass; const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): TServiceFactoryServer;
begin


  if (aImplementationClass=nil) or (high(aInterfaces)<0) then
    result := nil else
    result := (ServiceContainer as TServiceContainerServer).
      AddImplementation(aImplementationClass,aInterfaces,aInstanceCreation,nil,aContractExpected);
end;

function TSQLRestServer.ServiceRegister(aSharedImplementation: TInterfacedObject;
  const aInterfaces: array of PTypeInfo; const aContractExpected: RawUTF8): TServiceFactoryServer;
begin


  if (self=nil) or (aSharedImplementation=nil) or (high(aInterfaces)<0) then
    result := nil else
    result := (ServiceContainer as TServiceContainerServer).
      AddImplementation(TInterfacedClass(aSharedImplementation.ClassType),
        aInterfaces,sicShared,aSharedImplementation,aContractExpected);
end;

function TSQLRestServer.ServiceRegister(aClient: TSQLRest;
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): boolean;
begin
  result := False;
  if (self=nil) or (high(aInterfaces)<0) or (aClient=nil) then
    exit;


  result := (ServiceContainer as TServiceContainerServer).AddInterface(
    aInterfaces,aInstanceCreation,aContractExpected);
end;

function TSQLRestServer.ServiceDefine(aImplementationClass: TInterfacedClass;
  const aInterfaces: array of TGUID; aInstanceCreation: TServiceInstanceImplementation;
  const aContractExpected: RawUTF8): TServiceFactoryServer;
begin
................................................................................
  // nothing to do in this basic REST static class
end;

procedure TSQLRestStorage.EndCurrentThread(Sender: TThread);
begin // called by TSQLRestServer.EndCurrentThread
  // nothing to do in this basic REST static class
end;

function TSQLRestStorage.ServiceContainer: TServiceContainer;
begin
  result := nil;
end;

function TSQLRestStorage.CreateSQLMultiIndex(Table: TSQLRecordClass;
  const FieldNames: array of RawUTF8; Unique: boolean; IndexName: RawUTF8): boolean;
begin
  result := false; // not implemented in this basic REST static class
end;

................................................................................
      exit;
    Res.GetRowValues(0,TInt64DynArray(DataID));
    result := true;
  finally
    Res.Free;
  end;
end;

function TSQLRestClient.ServiceContainer: TServiceContainer;
begin
  if fServices=nil then
    fServices := TServiceContainerClient.Create(self);
  result := fServices;
end;


{ TSQLRecordLog }

destructor TSQLRecordLog.Destroy;
begin
  fLogTableWriter.Free;
................................................................................
  guid := aInterface^.InterfaceGUID;
  if guid=nil then
    raise EInterfaceResolverException.CreateUTF8('%.Create expects an Interface',[self]);
  fImplementationEntry := aImplementation.GetInterfaceEntry(guid^);
  if fImplementationEntry=nil then
    raise EInterfaceResolverException.CreateUTF8('%.Create: % does not implement %',
      [self,aImplementation,fInterfaceTypeInfo^.Name]);
  aInterface^.InterfaceAncestors(fInterfaceAncestors,aImplementation,
    fInterfaceAncestorsImplementationEntry);
  fImplementationClass := aImplementation;
  fImplementationClassIsCustomCreate :=
    aImplementation.InheritsFrom(TInterfacedObjectWithCustomCreate);
end;

constructor TInterfaceResolverForSingleInterface.Create(const aInterface: TGUID;
  aImplementation: TInterfacedObjectClass);
................................................................................
  if not fImplementationClassIsCustomCreate then
    result := fImplementationClass.Create else
    result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create;
end;

function TInterfaceResolverForSingleInterface.GetOneInstance(out Obj): boolean;
begin
  if self=nil then
    result := false else
    // here we now that CreateInstance will implement the interface 
    result := GetInterfaceFromEntry(CreateInstance,fImplementationEntry,Obj);
end;

function TInterfaceResolverForSingleInterface.TryResolve(
  aInterface: PTypeInfo; out Obj): boolean;
var i: integer;
begin
  if fInterfaceTypeInfo=aInterface then
    result := GetOneInstance(Obj) else begin
    // if not found exact interface, try any parent/ancestor interface
    for i := 0 to length(fInterfaceAncestors)-1 do
      if fInterfaceAncestors[i]=aInterface then begin
        // here we now that CreateInstance will implement fInterfaceAncestors[]
        result := GetInterfaceFromEntry(
          CreateInstance,fInterfaceAncestorsImplementationEntry[i],Obj);
        exit;
      end;
    result := false;

  end;
end;


{ TInterfaceResolverInjected }

var
  GlobalInterfaceResolutionLock: TRTLCriticalSection;
................................................................................
var i: integer;
begin
  for i := 0 to high(aStubsByGUID) do
    InjectResolver([TInterfaceStub.Create(aStubsByGUID[i])]);
end;

procedure TInterfaceResolverInjected.InjectResolver(
  const aOtherResolvers: array of TInterfaceResolver;
  OwnOtherResolvers: boolean);
var i: integer;
begin
  for i := 0 to high(aOtherResolvers) do
  if aOtherResolvers[i]<>nil then begin
    if aOtherResolvers[i].InheritsFrom(TInterfaceStub) then begin
      include(TInterfaceStub(aOtherResolvers[i]).fOptions,
        imoFakeInstanceWontReleaseTInterfaceStub);
      ObjArrayAdd(fResolversToBeReleased,aOtherResolvers[i]);
    end else
    if OwnOtherResolvers then
      ObjArrayAdd(fResolversToBeReleased,aOtherResolvers[i]);
    ObjArrayAddOnce(fResolvers,aOtherResolvers[i]);
  end;
end;

procedure TInterfaceResolverInjected.InjectInstance(
  const aDependencies: array of TInterfacedObject);
var i: integer;
................................................................................
  end;
end;

destructor TInterfaceResolverInjected.Destroy;
var i: integer;
begin
  try
    ObjArrayClear(fResolversToBeReleased);
    for i := 0 to length(fDependencies)-1 do
      IInterface(fDependencies[i])._Release;
  finally
    inherited Destroy;
  end;
end;

Changes to SynTests.pas.

647
648
649
650
651
652
653
654
655

656
657
658
659
660
661
662
class function TSynTestCase.RandomIdentifier(CharCount: Integer): RawByteString;
const CHARS: array[0..36] of AnsiChar =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
begin
  SetString(result,nil,CharCount);
  while CharCount>0 do begin
    result[CharCount] := CHARS[Random(High(CHARS))];
    dec(CharCount);

  end;
end;

class function TSynTestCase.RandomUTF8(CharCount: Integer): RawUTF8;
begin
  result := WinAnsiToUtf8(WinAnsiString(RandomString(CharCount)));
end;






<

>







647
648
649
650
651
652
653

654
655
656
657
658
659
660
661
662
class function TSynTestCase.RandomIdentifier(CharCount: Integer): RawByteString;
const CHARS: array[0..36] of AnsiChar =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
begin
  SetString(result,nil,CharCount);
  while CharCount>0 do begin

    dec(CharCount);
    PAnsiChar(Pointer(result))[CharCount] := CHARS[Random(High(CHARS)+1)];
  end;
end;

class function TSynTestCase.RandomUTF8(CharCount: Integer): RawUTF8;
begin
  result := WinAnsiToUtf8(WinAnsiString(RandomString(CharCount)));
end;

Changes to SynopseCommit.inc.

1
'1.18.1386'
|
1
'1.18.1387'