© John Kouraklis 2019
John KouraklisIntroducing Delphi ORMhttps://doi.org/10.1007/978-1-4842-5013-6_7

7. Aurelius on the Move

John Kouraklis1 
(1)
London, UK
 

It is common to use an ORM system to support desktop applications or the back end of servers. This is exactly what we have done so far. We have developed a FireMonkey desktop application and used Aurelius to access a local database storage.

In this chapter, we take the task of moving Aurelius to mobile platforms. You can use any kind of such platforms, be it mobile phones or tablets. In the projects we work on in this part of the book, I use an Android tablet running Android 7.0 Nougat.

Data Accessibility

In the first chapter, we discussed how ORM frameworks fit in three-tier applications (Figure 7-1). The CallCentre application we have been working on all this time does not explicitly treat those layers separately as we wanted to focus on Aurelius’ features. When you move to mobile applications (not web applications that can be accessed via browsers), the need to separate the concerns becomes more prominent as availability of resources such as storage and computational power may be in inadequacy.

Broadly speaking, when it comes to utilizing a database, there are two approaches you can take:
  • Use a local database: This can be a database file as the one we use in our application or a database server running on the mobile (e.g., Berkeley DB, Couchbase Lite, SQLite Server).

  • Use a remote server–based database: In this approach, the database is stored in a server in a remote location, and the mobile application sends queries to the server. This is usually implemented as REST client-server architecture, and the exchange of data conforms to predefined protocols (JSON, XML).

Aurelius can fit in all the preceding scenarios. In fact, once you use the framework, it is not difficult to switch between different designs or, even better, to use a combination of them; for example, you can use a local database to store some user-specific data and connect to a server for the main data sets.
../images/481234_1_En_7_Chapter/481234_1_En_7_Fig1_HTML.jpg
Figure 7-1

The role of ORM frameworks in three-tier applications (reproduced from Chapter 1)

Local Database

Moving CallCentre to an Android installation with a local database is not very hard to do. Our mobile solution implements the arrangement in Figure 7-1 as an application that incorporates all the layers and uses a local SQLite database. The full Delphi project can be found in the Call Centre – Local folder in the code of this chapter, and you can find a project to start working on your own in the Call Centre folder.

Our project is a FireMonkey application and, therefore, you should be able to build it for Android and execute it. From the perspective of Aurelius, we need to make a small adjustment. When we configured the TAureliusConnection in ConnectionModule.pas, we hardcoded the location and the name of the database file by adding the value of “.database.db” in the Database field in the wizard of the connection.

On Windows, this means that the database file is created in the same directory as the executable (binary) file. On Android, there is a different policy in place and applications can write files only in very special locations managed by the operating system. In practice, this means that the path we have will not work, and in fact if you run the project you will see that it crashes when launched.

In order to fix this issue, we are going to ask the operating system to provide the appropriate location. Delphi provides many ways to do this; the simplest is to use the GetDocumentsPath function . The modification is as follows in TSQLiteConnection.CreateConnection function . As you can observe, I use the ANDROID conditional compiler directive to isolate the code.
unit ConnectionModule;
interface
...
implementation
...
uses
  ...,
  System.IOUtils;
class function TSQLiteConnection.CreateConnection: IDBConnection;
begin
{$IFDEF ANDROID}
  SQLiteConnection.AureliusConnection1.Params.Values['Database']:=
      TPath.Combine(TPath.GetDocumentsPath, 'database.db');
{$ENDIF}
  Result := SQLiteConnection.AureliusConnection1.CreateConnection;
end;
Figure 7-2 shows a screenshot of the CallCentre application on Android.
../images/481234_1_En_7_Chapter/481234_1_En_7_Fig2_HTML.jpg
Figure 7-2

The CallCentre application on Android tablet

Note

There are a few more changes that had to be done to make CallCentre run on Android, but I skip them, as they are not Aurelius related. To provide a guidance if you want to explore the code more, look at the changes in FrameImportbtImportClick event in MainForm.pas, the FileSelectForm.pas (which provides a simple replacement of TOpenDialog for Android), the importData method in Database.Import.pas, and the deployment of assets (Deployment Manager).

Remote Server–Based Database

A database can be hosted in a remote location accessible via HTTP protocols. In such cases, a remote server feeds data to the client applications using well-defined exchange rules and formats (distributed applications). Moreover, the server, most of the times, incorporates a large amount (if not all) of the business logic to lighten the work that needs to be done in the client application (Figure 7-3).
../images/481234_1_En_7_Chapter/481234_1_En_7_Fig3_HTML.jpg
Figure 7-3

Separation of the data and business layers in a client-server arrangement

Aurelius, as an ORM framework, facilitates the access to the underlying databases. In a server-client design, there are more than one ways to utilize its capabilities. In Figure 7-4 you can see that the framework can fit in either the client or the server side.
../images/481234_1_En_7_Chapter/481234_1_En_7_Fig4_HTML.jpg
Figure 7-4

Aurelius can fit in both client and server sides of distributed applications

Client Side

When we look at a client application, which fetches data from a database, we are essentially dealing with exchange of data in a way that the client understands. Servers use JSON as the preferred (but not as the only) format to send data over the Internet, and our ORM framework needs to be able to decipher a JSON file and produce the required entities.

In order to demonstrate how Aurelius framework can assist in this direction, we are going to use a sample data set from JSONPlaceholder (2019) web site. JSONPlaceholder provides fake data sets accessible via a RESTful API. At the time of writing, the data sets include data for posts, comments, and users. There are, also, data sets for albums and to-do lists but we will not use them.
  1. 1.

    We are going to start by accessing the user with the ID 1. Open a browser and add the following line in the web address bar:

    https://jsonplaceholder.typicode.com/users/1

    JSONPlaceholder will respond with a JSON file like the one in Figure 7-5.
    ../images/481234_1_En_7_Chapter/481234_1_En_7_Fig5_HTML.jpg
    Figure 7-5

    JSON response for a user from JSONPlaceholder web site

     
  2. 2.

    The response reveals three JSON objects: the user itself, the address, and the company. Moreover, address object has a geo object with geographical coordinates.

     
  3. 3.

    Create a new console project and add a new unit under the name Entities.pas. If you wish to see the full project, check the User folder in the code files of this chapter.

     
  4. 4.
    In Entities.pas, add the following code:
    interface
    uses
      SysUtils,
      Generics.Collections,
      Aurelius.Mapping.Attributes,
      Aurelius.Types.DynamicProperties;
    type
      TAddress = class;
      TCompany = class;
      TGeolocation = class;
      TUsers = class;
      [Entity]
      [Table('Address')]
      [Id('Fid', TIdGenerator.None)]
      TAddress = class
      private
        [Column('id', [TColumnProp.Required])]
        Fid: Integer;
        [Column('street', [], 255)]
        Fstreet: string;
        [Column('suite', [], 255)]
        Fsuite: string;
        [Column('city', [], 255)]
        Fcity: string;
        [Column('zipcode', [], 255)]
        Fzipcode: string;
        [Association([], CascadeTypeAll - [TCascadeType.Remove])]
        [JoinColumn('geo', [], 'id')]
        Fgeo: TGeolocation;
      public
        property id: Integer read Fid write Fid;
        property street: string read Fstreet write Fstreet;
        property suite: string read Fsuite write Fsuite;
        property city: string read Fcity write Fcity;
        property zipcode: string read Fzipcode write Fzipcode;
        property geo: TGeolocation read Fgeo write Fgeo;
      end;
      [Entity]
      [Table('Company')]
      [Id('Fid', TIdGenerator.None)]
      TCompany = class
      private
        [Column('id', [TColumnProp.Required])]
        Fid: Integer;
        [Column('name', [], 255)]
        Fname: string;
        [Column('catchPhrase', [], 255)]
        F???catchPhrase: string;
        [Column('bs', [], 255)]
        Fbs: string;
      public
        property id: Integer read Fid write Fid;
        property name: string read Fname write Fname;
        property catchPhrase: string read FcatchPhrase write FcatchPhrase;
        property bs: string read Fbs write Fbs;
      end;
      [Entity]
      [Table('Geolocation')]
      [Id('Fid', TIdGenerator.None)]
      TGeolocation = class
      private
        [Column('id', [TColumnProp.Required])]
        Fid: Integer;
        [Column('lat', [], 255)]
        Flat: string;
        [Column('lng', [], 255]
        Flng: string;
      public
        property id: Integer read Fid write Fid;
        property lat: string read Flat write Flat;
        property lng: string read Flng write Flng;
      end;
      [Entity]
      [Table('Users')]
      [Id('Fid', TIdGenerator.None)]
      TUsers = class
      private
        [Column('id', [TColumnProp.Required])]
        Fid: Integer;
        [Column('name', [], 255)]
        Fname: string;
        [Column('username', [], 255)]
        Fusername: string;
        [Column('email', [], 255)]
        Femail: string;
        [Column('phone', [], 255)]
        Fphone: string;
        [Column('website', [], 255)]
        Fwebsite: string;
        [Association([], CascadeTypeAll - [TCascadeType.Remove])]
        [JoinColumn('company', [], 'id')]
        Fcompany: TCompany;
        [Association([], CascadeTypeAll - [TCascadeType.Remove])]
        [JoinColumn('address', [], 'id')]
        Faddress: TAddress;
      public
        property id: Integer read Fid write Fid;
        property name: string read Fname write Fname;
        property username: string read Fusername write Fusername;
        property email: string read Femail write Femail;
        property phone: string read Fphone write Fphone;
        property website: string read Fwebsite write Fwebsite;
        property company: TCompany read Fcompany write Fcompany;
        property address: TAddress read Faddress write Faddress;
      end;
    implementation
    initialization
      RegisterEntity(TGeolocation);
      RegisterEntity(TAddress);
      RegisterEntity(TUsers);
      RegisterEntity(TCompany);
    finalization
    end.
    In the preceding code, you can see familiar elements and there is nothing new, actually. The reason I include the full unit is to emphasize a few points in relation to the original JSON file the Placeholder site generated.
    1. a.

      The mapped members must have the exact case as they appear in the JSON file. The corresponding properties, though, can have a different case.

       
    2. b.

      The linked (associated) entities should not be declared as Lazy. The information in the JSON file as supplied by the web site is not enough for Aurelius to manage lazy loading.

       
    3. c.

      Nullable<T> data types should not be declared. This means all properties should be mandatory at database level.

       
     
  5. 5.
    Add the following code to retrieve the JSON file from the web site:
    ...
    uses
      System.SysUtils,
      IdHTTP;
    var
      idHTTP: TIdHTTP;
      response: string;
    begin
      try
        idHTTP:=TIdHTTP.Create(nil);
        response:=idHTTP.Get('http://jsonplaceholder.typicode.com/users/1');
        idHTTP.Free;
      except
        ...
      end;
    end.

    If you debug the code, you will see that response gets the full JSON file. Additionally, as a quick note, I use the http protocol rather than the https to simplify the use of TIdHTTP component .

     
  6. 6.
    Now, let us reconstruct the user entity. In order to do this, we need help from Aurelius. The framework provides a helper class in BCL.JSON unit, which is capable of serializing and deserializing objects in JSON format. The helper function uses generics to infer the properties of an object and then produces a string with the JSON representation. The usage is straightforward.
    ...
    uses
      System.SysUtils,
      IdHTTP,
      Entities in 'Entities.pas',
      Bcl.Json;
    var
      ...,
      newUser: TUsers;
    begin
      try
        ...
        newUser:=TJSON.Deserialize<TUsers>(response);
        newUser.Free;
        idHTTP.Free;
      except
        ...
      end;
    end.
    newUser holds now all the values from the JSON response. Aurelius created a new instance of TUsers as you can observe if you set a breakpoint and see the object in the debugger (Figure 7-6). At this stage, we’ve got an entity that can be managed in isolation or in a fresh Aurelius database virtual system.
    ../images/481234_1_En_7_Chapter/481234_1_En_7_Fig6_HTML.jpg
    Figure 7-6

    A new instance of JSON-created TUser object

     
  7. 7.
    The last thing we do is to free the newUser instance. However, if you run the code and check for memory leaks, you will see there are some bytes we have not cleaned up. The reason is that TUser links to two other objects (TAddress and TCompany) and TAddress to another one (TGeolocation). We have to explicitly free them too.
    begin
      try
        ...
        newUser:=TJSON.Deserialize<TUsers>(response);
        newUser.address.geo.Free;
        newUser.address.Free;
        newUser.company.Free;
        newUser.Free;
        idHTTP.Free;
      except
        ...
      end;
    end.
     
This demonstrates how easy it is to manipulate JSON files from external and third-party APIs. If you want to see how you can retrieve lists of objects from JSONPlaceholder, have a look at the Client folder in the code files of this chapter (AureliusClient project), which can run on multiple platforms (Figure 7-7).
../images/481234_1_En_7_Chapter/481234_1_En_7_Fig7_HTML.jpg
Figure 7-7

Aurelius deserialization of JSON files using JSONPlaceholder’s API

Server Side

Serving a REST server that uses Aurelius is similar to what we did in the previous section but in the opposite fashion. Instead of deserializing an object, we just serialize it to a JSON string.
...
  serialisedUser:=TJSON.SerializeAs<TUsers>(newUser);
...

XData

The previous examples demonstrate how to manage rather arbitrary JSON data structures, meaning that the structure of the generated JSON file does not adhere to any rules and it is really a matter of decisions made by the developers of JSONPlaceholder.

This may not pose a problem if the scale of the projects you are involved in is not big, but in general it is safer to use more standardized approaches. Open Data Protocol (OData, 2019) is a set of rules to build RESTful APIs, and a big part of it involves the definition of appropriate JSON file structure. TMS provides TMS XDATA, a product inspired by OData and smoothly integrated with Aurelius.

Aurelius is, deeply, intertwined with XData; and, this gives the advantage of generating, very quickly and efficiently, a server-client solution backed by a database. As a case, we are going to create a server that exposes the database and the entities from our CallCentre project and a client that retrieves the agents’ data. You can find the projects of this part in the XData folder in the code files.

Note

The projects in this section require TMS XData. This is a separate product sold by TMS. You can download a trial version from the TMS product page.

The server is able to access the database entities via a typical Aurelius connection as we have configured and used in the previous chapters.
  1. 1.

    After you have XData installed in your Delphi environment, go to FileNewOther…TMS XData and select TMS XData VCL Server (Figure 7-8).

     
../images/481234_1_En_7_Chapter/481234_1_En_7_Fig8_HTML.jpg
Figure 7-8

Selecting XData wizard from the File ➤ New menu in Delphi

That is pretty much what you have to do to create the server.
  1. 2.
    Go to Unit1.pas (or to Container.pas of the Server project in the XData folder) in the design mode and select the XDataServer component . In the object inspector, enter http://+:2001/callcenter in the BaseUrl field (Figure 7-9). This is the address we will use to access the database entities. In order for this to work correctly on Windows, the address needs to be registered with the operating system. If you want to see how to do this, please refer to the XData manual. Additionally, make sure you check the List and Get options in the DefaultEntitySetPermissions.
    ../images/481234_1_En_7_Chapter/481234_1_En_7_Fig9_HTML.jpg
    Figure 7-9

    The edited XDataServer properties

     
  2. 3.

    Configure AureliusConnection to use a local SQLite database.

     
  3. 4.

    Add the Entities.pas file from our main CallCentre project.

     
  4. 5.

    Run the project and make sure the server is running.

     
  5. 6.

    Open a web browser.

     
  6. 7.
    Enter http://localhost:2001/callcentre in the address bar and hit Enter. You will be able to see the available endpoints the server has generated (Figure 7-10). As you can observe, they represent the entity names of our database design. Aurelius has supplied them to XData server.
    ../images/481234_1_En_7_Chapter/481234_1_En_7_Fig10_HTML.jpg
    Figure 7-10

    XData retrieves the entity structure from Aurelius and makes it accessible via an API

     
  7. 8.
    Enter http://localhost:2001/callcentre/Agent in the address bar and hit Enter. Now we get a list of the agents (Figure 7-11). Aurelius has, successfully, fetched the data from the database and XData has exposed them in a JSON format.
    ../images/481234_1_En_7_Chapter/481234_1_En_7_Fig11_HTML.jpg
    Figure 7-11

    List of agents as generated by XData

     

The involvement of Aurelius finishes at this stage. XData provides the TXDataClient component that can be used in a client application and makes the manipulation of JSON data as easy as when you deal with Aurelius directly. In fact, it exposes a fluent interface that assimilates the Aurelius methods.

Let us create a simple project to consume the data XData sends. You can find the complete project under the name Client in the XData folder.
  1. 1.
    We need a form to show the data we receive like in the Figure 7-12. There is no reason to go through the details here. You can find the form if you like in the code files.
    ../images/481234_1_En_7_Chapter/481234_1_En_7_Fig12_HTML.jpg
    Figure 7-12

    Final form showing data retrieved via XDataClient

     
  2. 2.
    In the OnCreate and OnDestroy events of the button at the top of the form, add the following code to instantiate TXDataClient:
    ...
    interface
    type
      TForm1 = class(TForm)
        ...
        procedure FormDestroy(Sender: TObject);
        procedure FormCreate(Sender: TObject);
      private
        fClient: TXDataClient;
      public
        { Public declarations }
      end;
    ...
    implementation
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      fClient:=TXDataClient.Create;
    end;
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      fClient.Free;
    end;
     
  3. 3.
    In the OnClick event of the button at the top of the form, we pass the server address to the XDataClient and call loadAgents and, consequently, loadData. We also need to use the Database.Utilities unit .
    type
      TForm1 = class(TForm)
        ...
        procedure btFetchClick(Sender: TObject);
      private
        ...
        procedure loadAgents;
        procedure loadData(const aGUID: string);
      public
        ...
      end;
    ...
    implementation
    uses
      ...,
      Database.Utilities;
    procedure TFormMain.btFetchClick(Sender: TObject);
    begin
      fClient.Uri:=edURL.Text;
      loadAgents;
    end;
    procedure TFormMain.loadAgents;
    var
      list: TList<TAgent>;
      agent: TAgent;
    begin
      ...
      list:=fClient.List<TAgent>;
      for agent in list do
      begin
        ...
      end;
      list.Free;
    end;
    procedure TFormMain.loadData(const aGUID: string);
    var
      ...,
      agent: TAgent;
    begin
      agent:=fClient.Get<TAgent, TGUID>(StringToGUID(aGUID));
      if Assigned(agent) then
      begin
        ...
      end;
    end;
     

I have omitted the code that updates the user interface for simplicity. The points to be noted refer to the highlighted parts in the preceding code. The code lines show how we can use XDataClient. A closer observation reveals that the format of the calls to generate the database entities from the JSON file is, exactly, the same as in Aurelius.

Summary

In this chapter, we considered how to move Aurelius to mobile platforms. There are some implications, and at the same time new opportunities arise. We moved the application we developed in the previous chapters to mobile platforms, and we explored how Aurelius can sit in either the client or the server side to serve different needs.

References

OData, 2019. Open Data Protocol. [Online] Available at: www.odata.org/ [Accessed 06 05 2019].

Placeholder, J., 2019. JSONPlaceholder. [Online] Available at: https://jsonplaceholder.typicode.com/ [Accessed 05 05 2019].

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.157.197