This blog post will focus on one way of augmenting data returned from a database using the ORM, serving this as a wellformed XML result to REST client’s using as little code as possible.
kbmMW’s ORM is pretty good at fetching data from a database based on a class.
Sometimes we want to augment the class with additional data, before returning the data to a client.
This we can use the virtual table attribute for.
We have a class TPerson, which is used by the ORM to persist and retrieve persons from the person database table.
The person might refer to a company, via a companyId which is a GUID. This is all straight forward.
[kbmMW_Table('name:person')] TPerson = class private FID:kbmMWNullable<string>; FName:kbmMWNullable<string>; FCompanyID:kbmMWNullable<string>; public [kbmMW_Field('name:id, primary:true, generator:shortGUID',ftString,40)] property ID:kbmMWNullable<string> read FID write FID; [kbmMW_Field('name:name',ftString,50)] [kbmMW_NotNull] property Name:kbmMWNullable<string> read FName write FName; [kbmMW_Field('name:companyId',ftString,40)] property CompanyID:kbmMWNullable<string> read FCompanyID write FCompanyID; end;
However lets say we want to provide an augmented REST interface to the person information, where we want to add additional fields, like company name.
[kbmMW_VirtualTable] [kbmMW_Root('person',[mwrfIncludePublic])] TAugmentedPerson = class private FID:kbmMWNullable<string>; FName:kbmMWNullable<string>; FCompanyID:kbmMWNullable<string>; FCompanyName:kbmMWNullable<string>; public [kbmMW_Field('name:id',ftString,40)] property ID:kbmMWNullable<string> read FID write FID; [kbmMW_Field('name:name',ftString,50)] property Name:kbmMWNullable<string> read FName write FName; [kbmMW_Field('name:companyId',ftString,40)] property CompanyID:kbmMWNullable<string> read FCompanyID write FCompanyID; [kbmMW_Field('name:companyName',ftString,50)] property CompanyName:kbmMWNullable<string> read FCompanyName write FCompanyName; end;
What you can see is that an additional class is defined, which purpose primarely is to marshal augmented TPerson data to REST clients.
We have told that the TAugmentedPerson (which by outset has no relation to TPerson class wise), is a virtual table, which means it does in fact not live in any databases, but it can still be used as the output for queries.
So lets put together an augmented query:
[kbmMW_Service('name:REST, flags:[listed]')] [kbmMW_Rest('path:/rest')] TsvcRest = class(TkbmMWCustomHTTPSmartService) public [kbmMW_Rest('method:get, path:persons')] function GetPersons:TObjectList<TAugmentedPerson>; end;
// Return augmented persons. function TsvcRest.GetPersons:TObjectList<TAugmentedPerson>; begin Result:=dmMain.ORM.QueryList<TAugmentedPerson>('SELECT p.ID as ID, p.Name as Name, '+ ' CompanyID, c.Name as CompanyName '+ 'FROM uData.TPerson p, uData.TCompany c '+ 'WHERE c.ID=p.Company'); end;
This will make a query, augmenting the person data with a company name and returning it as a JSON object to the REST client.
This is all well and fine.
But lets say that the REST interface wants to return this list of TAugmentedPerson’s as XML?
This is now easily done in kbmMW by adding a responseMimeType to the kbmMW_Rest attribute of the GetPersons method.
[kbmMW_Service('name:REST, flags:[listed]')] [kbmMW_Rest('path:/rest')] TsvcRest = class(TkbmMWCustomHTTPSmartService) public [kbmMW_Rest('method:get, path:persons, responseMimeType:application/xml')] function GetPersons:TObjectList<TAugmentedPerson>; end;
This will result in simple XML document representing the resulting list of TAugmentedPerson’s. You can ask kbmMW to add XML declarations, namespaces and types if you want to by adding the properties declared:true, typed:true to the kbmMW_Rest method attribute.
But looking at the XML, it is still not perfect. It’s outer node is called TObjectList<person>, which is not a terribly nice tag name for an XML node. We are missing a way to redefine how kbmMW is to name the TObjectList<TAugmentedPerson> class.
Usually one would use the kbmMW_Root attribute in combination with our own class definition, to specify any name changes to root elements when marshalling data into or out from object instances, similarly to how TAugmentedPerson was defined.
However since we do not define TObjectList<TAugmentedPerson> anywhere (it is implicitely being defined as the result from our ORM.QueryList call, we have no place to specify our settings/attributes for that particular class.
Next version of kbmMW provides a new kbmMW_Alias attribute which handles this issue.
Basically what it does is to declare any class as an attribute wise alias to any other class/classes, like this
[kbmMW_Root('persons',[mwrfIncludePublic])] [kbmMW_Alias] TAugmentedPersonList = class(TObjectList<TAugmentedPerson>);
The kbmMW_Alias can have zero or one argument. If an argument is given, it can be a class reference, or an array of class references. If no argument is given, kbmMW automaticallyh defines TAugmentedPersonList to be an alias to TObjectList<TAugmentedPerson> due to the class inheritance.
As we never define TObjectList<TAugmentedPerson> anywhere, we can not refer to it as a class reference, why we use kbmMW’s way to implicitely determine the class by not providing any arguments for the kbmMW_Alias attribute.
In reality we will usually never instantiate any TAugmentedPersonList instances. It is only being used as a “placeholder” for defining attributes (on the class level) on types we don’t directly declare ourselves, like the TObjectList<TAughmentedPerson>.
Now the xml will look pretty, with the outer node named <persons> containing a number of inner nodes named <person> which each of them includes the companyName in addition to other TPerson related data.
As a side note, the [kbmMW_VirtualTable] attribute can now also take an argument, namely the actual database class for which this class is a virtual class for.
It would be possible to define [kbmMW_VirtualTable(TPerson)]
It informs kbmMW about that any queries made for TAugmentedPerson (which is not really a table found in the database), where the ORM can not deduce from any kbmMW SQL query statement, where to pickup data from, then it should use TPerson as the goto data table.
So this is now legal:
var ap:TAugmentedPerson; begin ap:=ORM.Query<TAugmentedPerson>(['Name'],['Kim']); end;
It will return first found record in the person table, which matches the person named Kim and return that as a TAugmentedPerson instance.
Only fields matching will be filled. Hence in this case the CompanyName value is null since we did not provide any value for it via the query.
But we are getting an object instance which allows us to add our own value for CompanyName, thus in practice augmenting the TPerson look alike object with additional information.