You are not logged in.
So, I can't handle transactions manually on the server side in case of client timeouts?
Unfortunately, this wouldn't work for us.
We need to control the database transaction on the server-side.
We have multiple mormot servers running in parallel (AWS AutoScaling) and receiving the load from a Elastic Load Balancer, so, client requests can hit different servers.
The problem that we are trying to solve is this: A client made a destructive call to the server (Insert/Delete/Update) but the connection was interrupted (client timeout, etc.). In that case, the client would assume that none of the changes are applyed on the database, so, we would like to rollback the transaction if anything goes wrong.
I can try that. AFAIK, the `CallbackReleased` method is used and handled internally by the framework, you are suggesting that I handle manually that behavior with this new method?
Why not just check for interfaceName='INotificationCallback' then delete the callback: IInvokable from all matching entry in fConnected[]?
that will work, but, I will have more than a thousand channels with potentialy more than 5k callbacks, it would be slow, don't you think? A hashlist would be much more efficient.
Or not use a dictionary, but an array of records containing the client string and the INotificationCallback entry.
Not sure, but will end up in the same problem, I think.
I'm trying to implement a publish-subscribe messaging system with mORMot websockets. I need something like (pusher) channels.
I want to use a unique mormot server to send messages to all clients, so, when a client subscribe to a message/event, it should be only for a determined channel/client. When a notification is sent, it is only for a specific client.
My interfaces look like this:
INotificationCallback = interface(IInvokable)
['{F5D956A4-AE70-4F47-AECC-40E2AB6AC0B4}']
procedure NotifyRefresh(const message: string);
end;
NotificationCallbackArray = array of INotificationCallback;
INotificationService = interface(IServiceWithCallbackReleased)
['{C92DCBEA-C680-40BD-8D9C-3E6F2ED9C9CF}']
procedure Subscribe(const client: string; const callback: INotificationCallback);
procedure Refresh(const client, message: string);
end;
I'm using a dictionary to store the callbacks:
fConnected: TDictionary<string, NotificationCallbackArray>;
...
procedure TNotificationService.Subscribe(const client: string; const callback: INotificationCallback);
begin
if not fConnected.ContainsKey(client) then
fConnected.Add(client, []);
InterfaceArrayAdd(fConnected[client], callback);
end;
The problem is the CallbackReleased method:
procedure CallbackReleased(const callback: IInvokable; const interfaceName: RawUTF8);
It doesn't provide any information that allow me to identify and release the correct callback.
Any ideas of how to solve this?
Thanks Daniel,
I was able to change the headers by overriding the InternalRequest on TSQLHttpClient.
function InternalRequest(const url, method: RawUTF8; var Header, Data, DataType: RawUTF8): Int64Rec; override;
I have some methods that are called in the DBX layer, some sort of a IAPPServer crazy wrapper, so, it will be much easier to send the session id on a HTTP Header rather than a explicit parameter.
In fact, It is perfectly possible to make a full stateless server, but, I will have to send some information from the client (like the user id) in every request, which is fine if I can send via Header or something like that, but, if I have to send those params explicitly in every method, it will became a mess.
About the Load Balancer, use mORMot can be a good idea, but, It will bring me some new problems. The ELB automatically adjust itself according to the traffic, if I use mORMot I will have to use a fixed EC2 machine that can support our peak usage (primarily network bandwidth), which can became expensive. ELB also gives me a free failover system. If one of the servers became offline, it will automatically launch a new server. I think this will be a lot more complicated to do without ELB.
The major problem is that the load balancer (AWS ELB) will send the request to any of the servers and it is not predictable. So, I can't rely on any in-memory information/session on the server side.
This server will serve a ton of clients with a low usage per client, so, I'm using sicPerThread.
I could use a modified version of TSQLRestServerAuthenticationHttpBasic, overriding the CheckPassword method, but I have one problem. AFAIK, mORMot save the authentication details (token/signature) in memory, and this would not work for me.
I will have more then one server running (Using AWS Auto-scaling/Load Balancer), so, I need to persist that data on Redis.
Any ideas how to do that?
I'm using only the http layer of mormot (Interface based services) in a legacy (ex-datasnap) project. I'm developing my own authentication methods (because of compatibility issues) and I want to send a session token from the client to the server in every request. I guess I can do that with a custom header.
Which is the right way to do this?
I didn't find any Headers property. I tried to override some internal methods of TSQLHttpClient but without success. I guess I'm looking in the wrong place.
haha, I'm glad you liked it. mORMot is awesome, I switched to another job two years ago (working mostly with Ruby, Go) and no longer had the opportunity to work with it, maybe this will change now.
Yeah, I'm doing some tests with Interfaced Based Services now. I was able to serialize the olevariant data and delta with some crazy code (http://stackoverflow.com/questions/2481 … conversion)
I think I will do just a tiny benchmark to compare to our Datasnap TCP, just for fun.
In this case, I want to serialize the ClientDataSet Data and Delta properties.
The problem with DatasetToJSON danielkuettner is the JSON, in first place, I would prefer something more optimized for speed. The other problem is that I don't have a method that does the inverse. JSONToClientDataSet dose'n work because of the blobs base64 encoding, as stated on the first post.
Unfortunately, we have some process (SQL) that have to be made on the server (actualy using datasets events), so, I don't think this will work for us.
Do you have any idea how I can serialize and send a OleVariant through TSQLRestServer?
I was able to make it work with SynDBDataset. Thanks.
Using SynDBRemote, I have access to any connection implementation (TFDConnection, TSQLConnection) that allows me to use the existing datasets with it?
Thank you, I will take a look in SynDBRemote.
Hello,
I'm trying to do some crazy stuff with mORMot.
I'm working on a project that is basically a Datasnap TCP stateful (IAPPServer) with a lot of DBX (Datasets, Clientdatasets, etc) which I can not get away from, despite wanting to.
We are having serious trouble to scale this thing, so, we want to make a stateless REST server. Amazingly, this is not simple to do with Datasnap REST, I mean, it is possible, but, I will have to do manually all the conversion Dataset -> JSON, JSON -> Dataset (also deltas). So, I'm thinking, If I have to do this, why not use mORMot in the HTTP layer?
First, I tought in use only the ninja JSON serialization of mORMot, but I think it would be very nice to use the http layer too. I am even wondering if I can communicate using binary (I don't have to access this data in any other client, so, the fastest, the better.).
Do you guys have any insight about this?
I haven't tried anything on the HTTP layer yet, but I had some trouble with the JSON serialization and datasets, I'm trying to use the existing datasets helpers. Probably this wasen't the purpose of this classes, or, I just using them wrong, I'm not sure. I tried to do thinks like this:
function TMormotTest.DatasetToJson(Dataset: TDataSet): string;
begin
Result := SynVirtualDataset.DataSetToJSON(Dataset);
end;
function TMormotTest.JsonToDataSet(json: string): TDataSet;
var
Dataset: TClientDataSet;
begin
Dataset:= TCDSUtil.GetEmptyDataSet;
JSONToClientDataSet(Dataset, json, nil, cdsReplace, false);
result := Dataset;
end;
The SynVirtualDataset.DataSetToJSON converts the blob fields to base64 and the JSONToClientDataSet does not take this into consideration.
I could not find an example of using the IN operator with FillPrepare (It's a little tricky search for it.)
I tried this way but it does not work:
var
oMeuProduto: TProduto;
begin
oMeuProduto := TProduto.Create;
try
oMeuProduto.FillPrepare(BaseDeDados, 'estoque IN (?)', [1, 0, 2, 41] );
while (oMeuProduto.FillOne) do
begin
Log(IntToStr(oMeuProduto.ID));
end;
finally
FreeAndNil(oMeuProduto);
end;
end;
I am using a external SQLite3 database.
I realized that i have not tested exactly what you passed in the other post, so, i did other test with this code:
produto.DestList.FillPrepareMany(DatabaseServer, 'Dest.Codigo=?', [], [1000]);
this error occurs: 'near "and": syntax error';
still have the same problem:
produto.FillPrepareMany(DatabaseServer, 'Dest.Codigo=?', [], ['1000']);
Error: 'no such column: Dest.A.Codigo'
I am trying to use the FillPrepareMany but isen't working. Someone can help me?
These are my entities classes.
TFornecedor = class(TSQLRecord)
private
FCodigo: integer;
FNome: RawUTF8;
published
property Codigo: integer read FCodigo write FCodigo;
property Nome: RawUTF8 read FNome write FNome;
end;
TProdutoXFornecedor= class(TSQLRecordMany)
private
fSource: TProduto;
fDest: TFornecedor;
published
property Source: TProduto read fSource;
property Dest : TFornecedor read fDest;
end;
TProduto = class(TSQLRecord)
private
FDescricao: RawUTF8;
FCodigo: integer;
FDepartamento: TDepartamento;
FProdutoXFornecedor: TProdutoXFornecedor;
FDataCadastro: TDateTime;
published
property Codigo : integer read FCodigo write FCodigo; stored AS_UNIQUE;
property Descricao : RawUTF8 index 40 read FDescricao write FDescricao;
property Departamento : TDepartamento read FDepartamento write FDepartamento;
property DestList : TProdutoXFornecedor read FProdutoXFornecedor;
property DataCadastro : TDateTime read FDataCadastro write FDataCadastro;
end;
I am trying to use in this way:
produto.FillPrepareMany(DatabaseServer, 'DestList.Dest.Codigo=?', [], ['1000']);
I receive an exception with this message:
ESQLite3Exception with message 'no such column: DestList'
What i am doing wrong?
Why on earth does not TObjectList<T> have a member to retrieve the type information about T ?
I had this same problem today! It's unfortunate.
I solved getting the type of the "First" method.
Rtti.GetType(ObjectList.ClassInfo).GetMethod('First').ReturnType
I know, it's very strange. But so far I have not found a better way.
I started to have problems with this backup method. Are experiencing the same error reported in these other posts.
http://synopse.info/forum/viewtopic.php?id=881
http://synopse.info/forum/viewtopic.php?id=643
http://synopse.info/forum/viewtopic.php?id=662
"cannot VACUUM - SQL statements in progress"
We do not perform any SQL command before running the backup. Simply open the program and ran the backup.
What is the correct way to back up an external database?
Today I ran the same software again and got some new errors. I have the latest mORMot version (updated-to: 21a1dd69da94b5736f090f533681abeefcb8460b 2013-07-29 16:18:35 UTC)
When running the test with 4 threads i'm getting this message:
{
"ErrorCode": 408
"ErrorText": "Request Timeout"
}
I am in doubt between using the internal database or external in mORMot.
From what I noticed, there are many different procedures that when you use an external database. Correct?
Say I choose to use the internal SQLite database. Can I switch to external later without changing the whole software?
Thanks
Only found this option. Is that correct?
TSQLDBSQLite3Connection(Props.MainConnection).DB.Backup('teste.bkp');
Is there any way to restore this backup in the framework?
It has some class that provides a method similar to BackupGz with compression?
I'm testing methods for backup a SQLite database. More precisely BackupGZ.
This is my code:
var
Props:TSQLDBConnectionProperties;
begin
Model := CreateBigSampleModel;
Props := TSQLDBSQLite3ConnectionProperties.Create('Teste.db3' , '', '', '');
VirtualTableExternalRegister(Model,TSQLBigSampleData,Props, 'SampleRecord');
Database := TSQLRestClientDB.Create(Model,nil,'MainDb.db3',TSQLRestServerDB,false,'');
TSQLRestClientDB(Database).Server.DB.Synchronous := smOff;
TSQLRestClientDB(Database).Server.DB.LockingMode := lmExclusive;
TSQLRestClientDB(Database).Server.CreateMissingTables(0);
TSQLRestClientDB(Database).Server.BackupGZ('Backup.gz');
end;
Apparently he is backing up MainDB.Db3 and not the actual base Teste.db3.
If I use this:
Database := TSQLRestClientDB.Create(Model,nil,':memory:',TSQLRestServerDB,false,'');
An error occurs when backing up.
What is the correct way to backup a External SQLite database?
I'll wait for the new results.
I ran the performance tests (15 - External DB performance) with Delphi XE4 on my machine and I had very different (Insertion speed) results from yours. Can you help identify why?
Basis for comparison (AB Notebook).
Direct Batch Trans Batch Trans
SQLite3 (file full) 493 494 88284 108877
SQLite3 (file off) 923 908 93613 117952
SQLite3 (ext full) 498 504 101296 128885
SQLite3 (ext off) 966 930 83708 108700
My PC (SSD)
Direct Batch Trans Batch Trans
SQLite3 (file full) 83 91 53851 67388
SQLite3 (file off) 1886 2053 108731 143282
SQLite3 (ext full) 99 87 70905 60120
SQLite3 (ext off) 2225 2303 125966 164235
My PC (HD)
Direct Batch Trans Batch Trans
SQLite3 (file full) 7 7 19804 20062
SQLite3 (file off) 2500 1841 112115 141330
SQLite3 (ext full) 6 6 17333 21157
SQLite3 (ext off) 2497 2138 123499 159877
Hardware configuration
Intel Core i7 3770 3.4Ghz (4Cores/8Threads)
SSD 120Gb Sandisk
HD Seagate Barracuda 7200
8Gb Ram DDR3
I disabled the antivirus.
What worries me more are the smFull tests.
Something very strange happened here.
I set up an environment for remote debugging with Delphi 6 and Wine to try to identify this problem (Never thought it would work, but it works! ).
I removed all of the mORMot units and the application continued not starting.
I removed all the code from the project, it was just the Form. And nothing.
I recreated the project from scratch, including the same things of the previous design. And it worked!
I have no idea what happened.
Thank you Arnaud!
Sorry. I thought was in last version. But there were some read-only sources (my fault). When performing "Fossil Update" he did not warned me. I thought it was updated, but it was not.
In this example, I do not have any communication (for now). It is a standalone application.
The only objects instantiated are TSQLRestServerDB and TSQLModel. But the problem seems to be before that, because the application does not start. Does not run any of my codes.
Must be something in a Initialization clause.
I can send you the source if it is useful.
I'm trying to do some tests to compare the performance of SQLite vs Firebird Embedded.
I wish the two worked with the same kind of synchronization.
According to this article (http://blog.synopse.info/post/2013/06/1 … cking-mode) I should use this:
better Security:
Client.Server.DB.LockingMode := lmExclusive;
Client.Server.DB.Synchronous := smFull;
best Performance
Client.Server.DB.LockingMode := lmNormal;
Client.Server.DB.Synchronous := smOff;
Is that right?
The first problem is that I have not found the property "DB.LockingMode".
Have any way to set the framework to work the same way with the two databases? To be a fair test!
Sorry about that.
Now trying to ZDBC.
var
Props:TSQLDBConnectionProperties;
begin
Model := CreateSampleModel;
Props := TSQLDBZEOSConnectionProperties.Create( TSQLDBZEOSConnectionProperties.URI(dFirebird, 'fbembed.dll'), 'mormot.fdb', '', '');
VirtualTableExternalRegister(Model,TSQLSampleRecord,Props, 'SampleRecord');
Database := TSQLRestServerDB.Create(Model, 'Application');
TSQLRestServerDB(Database).CreateMissingTables(0);
I noticed some problems.
1) Apparently the word "Time" is a reserved word in firebird. When trying to add the table SampleRecord an error occurs. A boring coincidence.
Simply changed the name of the property and is working.
2) The command to count the records in the table (TSQLRestServerDB(Database).TableRowCount(TSQLSampleRecord)) does not seem to be working. Always returns zero. I checked and there is data in the table.
After the first test (http://synopse.info/forum/viewtopic.php?id=1300), the mORMot REST client is working perfectly with Wine. Just had to add WinHTTP DLLs in wine.
Now I'm testing some other features in an application made in Delphi 6. This application runs on Wine 1.01 on some clients.
Basically the idea is to use SQLite instead of BDE.
I did two test applications, one in Delphi XE4 and another in Delphi 6.
The application made in Delphi XE4 works perfectly. The application made in Delphi 6 does not open in Wine.
The application is frozen in the background. The status in the linux task manager shows "PIPE_WAIT".
do you have any idea what can be?
These are the sources of the framework being used in the project:
mORMot.dcu
mORMotDB.dcu
mORMotSQLite3.dcu
SampleData.dcu
SynCommons.dcu
SynCrypto.dcu
SynDB.dcu
SynDBFirebird.dcu
SynLZ.dcu
SynSQLite3.dcu
SynSQlite3Static.dcu
SynSSPIAuth.dcu
SynZip.dcu
I tried to use the same way as the example 15 - External DB performance does, but there is another error.
var
Props:TSQLDBConnectionProperties;
begin
Model := CreateSampleModel;
Props := TSQLDBFirebirdEmbeddedConnectionProperties.Create('firebirdembedded.1', '', '', '');
VirtualTableExternalRegister(Model,TSQLSampleRecord,Props, 'SampleRecord');
Database := TSQLRestServerDB.Create(Model, ':memory:');
TSQLRestServerDB(Database).CreateMissingTables(0);
end;
This error also occurs in example 15.
Stack
exception class : ESQLite3Exception
exception message : Firebird Error 140000F9: Dynamic SQL Error. SQL error code = -502. Invalid cursor declaration. Statement already has a cursor SYNDB assigned. [ SQLCODE=-502 (The cursor identified in an OPEN statement is already open.) ].
main thread ($50bc):
006ce2ce +02e Project1.exe SynSQLite3 3890 +2 sqlite3_check
006cde05 +041 Project1.exe SynSQLite3 3818 +3 TSQLRequest.Step
006cd3bd +035 Project1.exe SynSQLite3 3439 +5 TSQLRequest.Execute
006cd46a +02e Project1.exe SynSQLite3 3459 +3 TSQLRequest.Execute
006cc048 +074 Project1.exe SynSQLite3 2847 +9 TSQLDatabase.Execute
006cf150 +144 Project1.exe mORMotSQLite3 752 +16 TSQLRestServerDB.CreateMissingTables
what I'm doing wrong?
I'm trying to connect mORMot to a firebird embedded database.
This is the code:
var
Props:TSQLDBConnectionProperties;
begin
Model := CreateSampleModel;
Props := TSQLDBFirebirdEmbeddedConnectionProperties.Create('localhost:C:\TESTE2.fdb', 'TESTE2.fdb', 'SYSDBA', 'asd');
VirtualTableExternalRegister(Model,TSQLSampleRecord,Props, 'SampleRecord');
Database := TSQLRestServerDB.Create(Model, ':memory:');
TSQLRestServerDB(Database).CreateMissingTables(0); // ERROR
end;
CreateMissingTables returns this error. What is very strange since the firebird embedded does not have authentication.
Stack:
exception class : ESQLite3Exception
exception message : Firebird Error 14000098: Your user name and password are not defined. Ask your database administrator to set up a Firebird login. [ SQLCODE=-902 (Unsuccessful execution caused by a system error that precludes successful execution of subsequent statements) ].
main thread ($21bc):
006ce2ce +02e Project1.exe SynSQLite3 3890 +2 sqlite3_check
006cde05 +041 Project1.exe SynSQLite3 3818 +3 TSQLRequest.Step
006cd3bd +035 Project1.exe SynSQLite3 3439 +5 TSQLRequest.Execute
006cd46a +02e Project1.exe SynSQLite3 3459 +3 TSQLRequest.Execute
006cc048 +074 Project1.exe SynSQLite3 2847 +9 TSQLDatabase.Execute
006cf150 +144 Project1.exe mORMotSQLite3 752 +16 TSQLRestServerDB.CreateMissingTables
Can help me?
I think I know what caused the problem. During the test, the database was loaded in SQLite Expert. I did not realize that. My mistake. Apparently it's working as expected with SQLRestServerDB.
So, I should use the TSQLRestClientDB?
I tried this way:
Database := TSQLRestClientDB.Create(Model, Model, ChangeFileExt(paramstr(0),'.db3'), TSQLRestServerDB);
TSQLRestClientDB(Database).Server.CreateMissingTables(0);
but...sometimes this error occurs:
{"ErrorCode":408,"ErrorText":"Invalid Request"}
thank you very much!
I have a problem. What am I doing wrong?
I have this code (Using OmniThread Library):
procedure TForm1.FormCreate(Sender: TObject);
begin
Model := CreateSampleModel;
Database := TSQLRestServerDB.Create(Model, ChangeFileExt(paramstr(0),'.db3'));
TSQLRestServerDB(Database).CreateMissingTables(0);
end;
procedure TForm1.AddValues;
var Rec: TSQLSampleRecord;
i: Integer;
begin
for i:=0 to 100 do
begin
Rec := TSQLSampleRecord.Create;
try
Rec.Name := GetCurrentThreadId().ToString + GetCurrentTime.ToString + i.Tostring;
Rec.Question := StringToUTF8('TESTE - '+GetCurrentThreadId().ToString + GetCurrentTime.ToString + ' Loop:'+i.ToString);
if Database.Add(Rec,true)=0 then
raise exception.create('Error adding the data');
finally
Rec.Free;
end;
end;
end;
procedure TForm1.BtnParallelClick(Sender: TObject);
var
i: Integer;
begin
for i := 0 to 20 do
Parallel.Async(AddValues);
end;
Sometimes when running BtnParallelClick I encounter this error: ESQLite3Exception with message 'database is locked';
I need to decide which of these embedded databases use in an point of sale application. It's an important decision because it will affect 5000 users.
From what I've seen the Firebird is faster (http://ea.tl/embeddeddb.shtml). But what really worries me is support for multithreaded and data corruption.
What is your experience about it? Which one would you choose?
1. Can I use the set mORMot + SQLite3 with multiple threads?
I ask this because through the documentation I could not identify what, exactly, is thread-safe and what is not.
SQLite3 documentation: ".. in order to be thread-safe, SQLite must be compiled with the SQLITE_THREADSAFE preprocessor macro set to 1"
mORMot documentation: "SQLite3 source code was compiled without thread mutex: the caller has to be thread-safe " / "low level sqlite3_ aware * () functions are not thread-safe" / "SQLITE_THREADSAFE 0 in sqlite.c"
Is there any example of multiple threads with a mORMot client?
Thank you Arnaud. I'll test.
With Delphi XE2 and XE4 an error occurs when registering a service using TSQLHttpClientWinSock;
exception class : EServiceException
exception message : IControleEstacionamento interface or REST routing not supported by server:
Stacktrace:
0068ac07 +11f Project2.exe mORMot 32873 +14 TServiceFactoryClient.Create
0068431d +075 Project2.exe mORMot 29858 +6 TServiceContainer.AddInterface
006724e9 +05d Project2.exe mORMot 21723 +6 TSQLRestClientURI.ServiceRegister
0069593c +12c Project2.exe uClientMOrmothttp 59 +9 TClienteServidorSysmoEstacionamento.Conectar
works using TSQLHttpClient.
My interface:
IControleEstacionamento = interface(IInvokable)
['{9D3E34D0-68D2-4095-9989-49021D89C2D0}']
procedure CalcularDebitoTicket( ATicket: Int64; AOperador: Integer);
procedure CalcularDescontoCupomFiscal(ATicket: Int64; ACupom, APDV: Integer;
AData: TDateTime; AValor: Currency; AOperadorCaixa: Integer);
procedure PagarTicketLiberarCancela( ATicket: Int64; AOperadorCaixa: Integer );
procedure EstornarTicketEstacionamento( ATicket: Int64; AOperadorCaixa: Integer );
procedure EstornarDescontoCupomFiscal(ATicket: Int64; APDV, ACupom, AOperador: Integer; AData: TDateTime; AEmCupomFiscal: Boolean = False);
procedure LiberarTicketEstacionamentoSemPagar( ATicket: Int64; AOperadorCaixa: Integer );
function GetEstadoPapel: RawUTF8;
property EstadoPapel: RawUTF8 read GetEstadoPapel;
function NotificarStatusPapel: Boolean;
function RetornarDataHoraServidor: TDateTime;
function PossuiFaixaDesconto: Boolean;
end;
I can easily choose to use TSQLHttpClientWinSock instead of TSQLHttpClientWinHTTP?
I have some customers using our applications for windows on Linux with Wine. And is not working, I imagine the TSQLHttpClientWinSock solve it, but do not know how to use it.
I found this in SynCrtSock.pas:
{$ifdef MSWINDOWS}
{$define USEWININET}
/// define this to publish TWinINet / TWinHttp / TWinHttpAPI classes
{$else}
{$undef USEWININET} // WinINet / WinHTTP / HttpAPI expect a Windows system
{$endif}
and this in mORMotHttpClient:
{$ifdef USEWININET}
/// HTTP/1.1 RESTFUL JSON default mORMot Client class
// - under Windows, map the TSQLHttpClientWinHTTP class
TSQLHttpClient = TSQLHttpClientWinHTTP;
{$else}
/// HTTP/1.1 RESTFUL JSON deault mORMot Client class
// - not unders Windows, map the WinSock implementation class
TSQLHttpClient = TSQLHttpClientWinSock;
{$endif}
But I do not want to change the framework. Especially, compilation directives.
Calculator := Client.GetService<ICalculator>;
is a valid Delphi syntax, which I just implemented with a new TSQLRest.Service<T: IInterface> method.
For newer generic-aware versions of Delphi (i.e. Delphi 2010 and up, since Delphi 2009 is buggy about generics), you can use such a method, which enabled compile-time checking:
var I: ICalculator;
begin
I := Client.Service<ICalculator>;
if I<>nil then
result := I.Add(10,20);
end;
Excellent. Thank you.
See http://synopse.info/fossil/info/783a67b6d3
(feel free to propose any other syntax extension using generics)BUT
Client.GetService(ICalculator,Calculator);
will never compile.
There is not "class of IMyInterface" kind of type, as we have "class of TMyClass" in the Delphi syntax, AFAIK.
This is why we are required to use TypeInfo(ICalculator).In fact, Client.Service<ICalculator> is just a wrapper around Clients.Services.Info(TypeInfo(ICalculator)).Get().
It is true. Sorry.
The ICalculator is the contract.
It is to be shared between client and server.
It is IMHO a much better approach than generating a client.
In SOAP/WCF it is just a nightmare to generate clients wrappers, from my experiment.Whereas a shared unit with the interface type definition is just clean and easy.
That is ok. I agree.
You have to use the overloaded method, using the RTTI, i.e. TypeInfo(ICalculator).
Using generics, we may be able to write something like:
Client.GetService<ICalculator>(Calculator);
but I do not think it is better than:
Client.GetService(TypeInfo(ICalculator),Calculator);
... and will generate much more code.
Certainly any of these methods would already be great. Some of them already exist?
How I have not found this method in class TSQLHttpClient, I deduce not!
I like the implementation with generics, but as it only works in the newer versions of Delphi is not very useful for us.
An even simpler implementation without generics, might look like:
Client.GetService(ICalculator,Calculator);
with generics:
Calculator := Client.GetService<ICalculator>;
Thanks, I'll check.
Sometimes I do not know if you're for or against my implementation. Let's try to clarify.
AFAIK the client model can not contain all the classes available on the server.
Only the classes the client needs are expected to be part of the model.
Unless you use a TSQLRecordReference kind of field.
I think there may be cases where the client has access to all the classes available on the server. But that's okay. Let's say that the model (SQLModel) client has only a part of the classes that are on the server model. This will slightly change my implementation. But it is still possible.
Use case of the TSQLModel instance is to be defined in a given object context.
To ensure re-entrance of the classes.
Using such a global model will add a dependency, and will break the dependency inversion model for sure.
I do not understand what you mean by "Use case of the TSQLModel instance is to be defined in a given object context.";
My implementation does not differ so much from the implementation you used in the "CreateSampleModel". However, the "CreateSampleModel" has only one entity. In case you have multiple entities the implementation would be slightly different, I guess. And if not, the unit SampleData.pas (Ex 04) would have to know all the entities.
When you say that my implementation will add a dependency, you mean that all entities will depend on another unit, in my case, EntidadesPublicas.pas?
Regard this entities will have to be shared between several projects. Create a dependency of another unit, also shared, is a problem?
What would you change in my implementation?
You would drive EntidadesPublicas.pas know all the entities and create the model from them?
see MainDemo.
you can find that ab use TFileServer and TFileClient classes like your purpose.
I'm sorry, but this demo is very confusing.
I would like to allow the client to use a read-only entity. Is it possible?
Only the server will write data to that entity and I would like to ensure that developers do not implement a change in a client application.
Got it. I'm thinking of a way to facilitate the use of interfaces for developers. But not reached a conclusion yet. Imagine that developers who make the server are not the same that will implement the client.
Another thing. I'm not pleased about having to use an identifier of type String to get the service. Like that:
var
Calculator: ICalculator;
begin
Client.Services['Calculator'].Get(Calculator);
end;
It would not be possible to have something like this?
var
Calculator: ICalculator;
begin
Client.GetService(Calculator);
end;
Maybe we can get the identifier via RTTI, what do you think?