Most systems, especially in the DDD area, needs to integrate to a legacy system. In our case our we had to communicate to a Firebird 1.5 database.
The first step was to define our Data Transfer Objects:
type TLegacyID = type RAWUTF8; TLegacyAccount = class(TSynPersistent) private fLegacyID: TLegacyID; fDateModified: TDateTime; fUserCreated: RAWUTF8; fDateCreated: TDateTime; fName: RAWUTF8; fisActive: Boolean; public constructor Create; overload; override; constructor Create( aID : TLegacyID; aName : RAWUTF8; aIsActive : Boolean; aDateCreated, aDateModified : TDateTime; aUserCreated : RAWUTF8 ); overload; published property ID : TLegacyID read fRevelightID write fRevelightID; property Name : RAWUTF8 read fName write fName; property isActive : Boolean read fisActive write fisActive; property DateCreated : TDateTime read fDateCreated write fDateCreated; property DateModified : TDateTime read fDateModified write fDateModified; property UserCreated : RAWUTF8 read fUserCreated write fUserCreated; end; TLegacySupplier = class(TLegacyAccount) end;
Here we declare a unique type to identify our legacy IDs (strings). We also do a basic map of our data layout, with a customized constructor for ease of creation. One can add other methods later to handle copies and assignments.
The next step was to define our service:
type ILegacyStockQuery = interface(IInvokable) ['{2BDC9F78-B9C2-4621-A557-F87F02AC0581}'] function GetSupplier(const aID: TLegacyID; out Supplier: TLegacySupplier): TCQRSResult; end;
we’ll publish a service as LegacyStockQuery with a single method GetSupplier. This method will return a JSON encoded representation of our TLegacySupplier, ready to be consumed by a client.
To implement it:
type TLegacyStockQuery = class(TInterfacedObject, ILegacyStockQuery) private fDbConnection : TSQLDBConnectionProperties; public constructor Create( const aProps: TSQLDBConnectionProperties ); overload; function GetSupplier(const aID: TRevelightID; out Supplier: TLegacySupplier): TCQRSResult; property DbConnection : TSQLDBConnectionProperties read fDbConnection write fDbConnection; end;
We keep a copy of our database connection properties local to our instance to ensure thread safety.
{ TLegacyStockQuery } constructor TLegacyStockQuery.Create(const aProps: TSQLDBConnectionProperties); begin fDbConnection := aProps; inherited Create; end; function TLegacyStockQuery.GetSupplier(const aID: TLegacyID; out Supplier: TLegacySupplier): TCQRSResult; var Res, Address : ISQLDBRows; begin Result := cqrsNotFound; Res := fDbConnection.Execute( 'select * from SUPPLIERS where SUPPLIER_ID=? ', [aID] ); if Res.Step then begin Result := cqrsSuccess; Supplier.ID := Res['SUPPLIER_ID']; Supplier.Name := Res['SUPPLIER_NAME']; Supplier.isActive := Res['ACTIVE_FLAG'] = 'Y'; Supplier.DateCreated := Res['DATE_CREATED']; Supplier.DateModified := Res['DATE_MODIFIED']; Supplier.UserCreated := Res['USER_CREATED']; end; end;
Execute the query against the legacy database and populate the DTO. Using the ISQLDBRows interface means less object maintenance and cleaner code.
To kick the whole thing off we have:
procedure StartServer( aDbURI : RawURF8 ); var aDbConnection : TSQLDBConnectionProperties; aStockServer : TSQLRestServerFullMemory; aHTTPServer : TSQLHttpServer; begin aDbConnection := TSQLDBZEOSConnectionProperties.Create( aDbURI, '', '', '' ); aStockServer := TSQLRestServerFullMemory.Create([]); try aStockServer.ServiceDefine( TLegacyStockQuery.Create( aDbConnection ), [ILegacyStockQuery]); aHTTPServer := TSQLHttpServer.Create( DEFAULT_HTTP_PORT, [aStockServer] ); try aHttpServer.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries writeln('Background server is running.'#10); writeln('Cross-Platform wrappers are available at ', DEFAULT_HTTP_PORT ,'/', DEFAULT_SERVER_ROOT ); write('Press [Enter] to close the server.'); readln; finally aHTTPServer.Free; end; finally aStockServer.Free; end; end;
It would be better to use dependency injection here, but we’ll get in to that later.
When invoking it, we used a Zeos DB URI, like this one:
zdbc:firebird-1.5://tst-db-svr-1/srv/db/EXAMPLE.FDB?username=SYSDBA;password=masterkey
Tips!
Remember to register your Object array types with
TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(<ArrayType>),<ObjType>);
e.g.
TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(TLegacySupplierObjArray),TLegacySupplier);
Reblogged this on burningrump.
LikeLike
Did you manage to compile this, I tried but I got error on line 8 aStockServer := TSQLRestServerDB.Create;
The conatructor Create is expecting parameters.
LikeLike
Well spotted! Not sure how that managed to creep in there!
aStockServer should be of type TSQLRestServerFullMemory, and the constructor takes as a parameter a dynamic array of ORM classes to publish. Since we’re not using the ORM in this example, we pass in an empty array.
LikeLike
Thank you for your quick reply, I was trying to mimic the example here with my own DTO and my own database. If I was successful what would be the uri to test from a browser ? http://serverip:port/root/GetSupplier/25 ?
I used a model as parameter to the server constructor, inherit my DTO from TSQLRecord instead of TSynPersistent and I added a line VirtualTableExternalRegisterAll(model,aprops) it compiles. But I keep getting {
“errorCode”:400,
“errorText”:”Bad Request”
} on the browser.
when I tried to load http://localhost:8888/root/getUserSession/2
LikeLike
For interface based services, all the parameters are either POSTed to it, or retrieved from GET parameters. That means your URL will be:
http://serverip:port/root/GetSupplier?aID=25
alternatively you can POST
{aID:25}
tohttp://serverip:port/root/GetSupplier
LikeLike
Any advises about “Exception with message ‘DBError : [10007] : General SQL Server error: Check messages from the SQL Server. Unicode data in a Unicode-only collation or ntext data cannot be sent to clients using DB-Library (such as ISQL) or ODBC version 3.7 or earlier” ?
LikeLike
I need a bit more information. Are you connecting to a MSSQL database using Zeos?
LikeLike
IMHO you may better use the NewThreadSafeStatementPrepared method, at TSQLDBConnectionProperties level, instead of maintaining your own single connection. Or you should ensure that the whole service is executed in a lock, for thread safety.
LikeLike
Hi Ab,
From what I’ve seen, TSQLDBConnectionProperties.Execute, uses NewThreadSafeStatementPrepared? Am I missing something?
LikeLike
My mistake, I thought that aDbConnection was a connection, whereas it was a connection properties instance.
Variable name mislead me (this is why naming matters).
LikeLike
Hi Willio,
In your Interface example you have an example on how to retrieve Suppliers information with GetSupplier, how would you send a new supplier record to the legacy database?
What should be the parameters and how to handle the Json data received?
I would like to know how to code the function SetSupplier( Supplier: TLegacySupplier): TCQRSResult;
LikeLike
Hi Winslas ,
You wouldn’t need to handle the JSON, as mORMot already does that for you.
From the top of my headI’d do something like this:
function TLegacyStockQuery.SetSupplier(const Supplier: TLegacySupplier; out aID: TLegacyID): TCQRSResult;
begin
Result := cqrsNotFound;
(* Perform what ever steps you need to, to persist the Supplier, and populat ethe ID to be returned. *)
end;
LikeLike
Hello
How to create update (POST, PUT) with mORMot.
Thank in advance.
LikeLike
Hi Tuan,
You can use either GET or POST/PUT to access any of your published end points. Look at this article for more information:
http://blog.synopse.info/post/2014/01/07/Some-enhancements-to-REST-routing-of-interface-based-services
LikeLike
How do you solve the problem on a result that contains several records? I guess you use dynamic arrays but how does it fit ISQLDBRows?
LikeLike