TMS Aurelius and MVVM design: an example (Part 1)

Model-View-ViewModel (MVVM) design pattern allows developers to write software in a way that separates applications to layers that serve the business logic, the presentation logic and the elements of the user interface.

TMS Aurelius is an ORM framework developed and marketed by TMS which provides a great degree of abstraction, flexibility and simplicity. I have been using Aurelius for sometime now in different applications and I appreciate its benefits. But when I attempted to combine MVVM and Aurelius at first place, I fell into some traps mostly by a few misconceptions I had about the framework. The guys at TMS were very helpful and spent time replying to my emails. This helped me clarify and better understand how Aurelius has been designed.

In this post, I describe my approach to using Aurelius in MVVM with the hope it is useful and educative to others and, at the same time, with an invitation to others to share their approaches and improve my code and understanding.

The Example Application

The goal is to develop a FireMonkey application which meets the following requirements:

  • Shows a list of customers
  • Allows the user to add a new customer
  • Allows the user to edit an existing customer
  • Allows the user to delete an existing customer
  • Keeps the following data for each customer: first name, last name and a photo

The application uses the MVVM pattern and stores data in a SQLite database file, which is located in the local drive. The communication between the application and the database is done via Aurelius.

The application has two screen: the Main Screen and the Customer Screen. Figure 1 and Figure 2 show these screens.

customersmainwindow

Figure 1: The Main Screen

 

customerscustomerwindow

Figure 2: The Customer Screen

Both screens contain a frame to present the details of the customer (Figure 3).

customerpanel

Figure 3: The frame for the details of the customer

 

MVVM Design

There are many resources available which provide an explanation of the MVVM pattern but, of course, the best one can be found in my book :-). I will not go into details here since this post is, primarily, about Aurelius. I will, only, describe my approach to the example application under the MVVM paradigm.

Each screen in the application (View) is supported by a dedicated ViewModel and there is one Model which serves both ViewModels (Figure 4 and Figure 5).

mainscreenmvvm

Figure 4: MVVM design of the Main Screen

customerscreenmvvm

Figure 5: MVVM design of the Customer Screen

 

TMS Aurelius Design

There are three core concepts in Aurelius which define how the framework is designed and is being used:

  1. The Entities: They define the classes that correspond to the tables in the database
  2. The Connection: Defines the type of the database to be used, the chosen dialect and how the actual connection to the database takes place
  3. The Object Manager: Manages the entities and takes care of the database actions

The Entity

For our application, we have one object which represents a customer as seen in the following Figure 6.

tcustomertable

Figure 6: The Customer Entity

I keep the entities in a dedicated unit and, therefore, I declare Core.Database.Entities.pas unit to keep the TCustomer class.

unit Core.Database.Entities;
 
interface
 
uses
  SysUtils, Aurelius.Mapping.Attributes, 
  Aurelius.Types.Blob, Aurelius.Types.DynamicProperties, 
  Aurelius.Types.Nullable, Aurelius.Types.Proxy, 
  Aurelius.Criteria.Dictionary;
 
type
  TCustomers = class;
  TCustomersTableDictionary = class;
 
  [Entity]
  [Table('Customers')]
  [Id('FId', TIdGenerator.IdentityOrSequence)]
  TCustomers = class
  private
    [Column('ID', [TColumnProp.Required, TColumnProp.NoInsert, TColumnProp.NoUpdate])]
    FId: Integer;
 
    [Column('FirstName', [], 65535)]
    FFirstname: Nullable;
 
    [Column('LastName', [], 65535)]
    FLastname: Nullable;
 
    [Column('Photo', [TColumnProp.Lazy])]
    FPhoto: TBlob;
  public
    property Id: Integer read FId write FId;
    property Firstname: Nullable read FFirstname write FFirstname;
    property Lastname: Nullable read FLastname write FLastname;
    property Photo: TBlob read FPhoto write FPhoto;
  end;
 
 ...
 
implementation
 
...
 
end.

The Database Connection

We can very easily and in a direct way create the database manager and the database connection when the application starts but here I will try to develop code that promotes good design practices. I declare an interface which allows access to the database manager and the connection and develop the corresponding class. The following units show these two steps (Core.Database.Aurelius.Types and Core.Database.Aurelius).

unit Core.Database.Aurelius.Types;
 
interface
 
uses
  Aurelius.Engine.ObjectManager,
  Aurelius.Drivers.Interfaces,
  Aurelius.Engine.DatabaseManager;
 
  type
  IAurelius = interface
    ['{BC578F5C-4D4F-43CD-8B7C-C5D4A57CE164}']
    function getDatabaseConnection: IDBConnection;
    function getDatabaseManager: TDatabaseManager;
    procedure createDatabaseManager;
    property DatabaseConnection: IDBConnection read getDatabaseConnection;
    property DatabaseManager: TDatabaseManager read getDatabaseManager;
  end;
 
implementation
 
end.
unit Core.Database.Aurelius;
 
interface
 
uses
  Core.Database.Aurelius.Types;
 
function createAureliusDatabase (const path: string=''): IAurelius;
 
implementation
 
uses
  Aurelius.Engine.DatabaseManager,
  Aurelius.Drivers.Interfaces,
  Aurelius.Drivers.SQLite, 
  Aurelius.Sql.SQLite,
  System.SysUtils, 
  System.IOUtils;
 
type
  TAurelius = class (TInterfacedObject, IAurelius)
  private
    fDatabaseConnection: IDBConnection;
    fDatabaseManager: TDatabaseManager;
    fDatabasePath: string;
    function getDatabaseConnection: IDBConnection;
    function getDatabaseManager: TDatabaseManager;
    procedure createDatabaseManager;
  public
    constructor Create (const path: string='');
    destructor Destroy; override;
  end;
 
const
  databaseFilename='test.sqlite';
 
{ TAurelius }
 
constructor TAurelius.Create(const path: string);
begin
  fDatabasePath:=Trim(path);
  try
    fDatabaseConnection:=
    TSQLiteNativeConnectionAdapter.Create(
      TPath.Combine(fDatabasePath, databaseFilename));
   except
    raise Exception.Create('Local database can''t be created');
   end;
  createDatabaseManager;
  if not FileExists(TPath.Combine(fDatabasePath, databaseFilename)) then
    fDatabaseManager.BuildDatabase;
end;
 
procedure TAurelius.createDatabaseManager;
begin
  if Assigned(fDatabaseConnection) then
    fDatabaseManager:=TDatabaseManager.Create(fDatabaseConnection)
  else
    raise Exception.Create('Database Connection is nil');
end;
 
destructor TAurelius.Destroy;
begin
  fDatabaseManager.Free;
  inherited;
end;
 
function TAurelius.getDatabaseConnection: IDBConnection;
begin
  Result:=fDatabaseConnection;
end;
 
function TAurelius.getDatabaseManager: TDatabaseManager;
begin
  Result:=fDatabaseManager;
end;
 
function createAureliusDatabase (const path: string=''): IAurelius;
begin
  Result:=TAurelius.Create(path);
end;
 
end.

This may look complicated and unnecessary but it allows for expansion. With very little modification, you can create subclasses of TAurelius to handle local databases and databases in memory very easily and take advantage of dependency injection to choose the correct database type according to your needs.

In our application, the database needs to be available in all our units. There are a few ways to do this (global variables, singleton, etc.). For simplicity, I declare a global variable in Model.Database.pas unit.

unit Model.Database;
 
interface
 
uses
  Core.Database.Aurelius.Types;
 
var
  database: IAurelius;
 
implementation
 
uses
  Core.Database.Aurelius;
 
initialization
  database:=createAureliusDatabase;
end.

At this stage, the application creates the database file in the Win32/Debug folder or in the relevant folder depending on the target and platform you have chosen to compile the project.

The Object Manager

The last part we need is the object manager which allows the real manipulation of the database records and tables. We have to be able to create an instance of the object manager and because this is, directly, linked to the database manager, we get this instance from the same place we find the database manager (ie, the TAurelius class). We do this by adding the necessary code in the relevant units.

unit Core.Database.Aurelius.Types;
 
interface
 
uses
  ...
 
  type
  IAurelius = interface
    ['{BC578F5C-4D4F-43CD-8B7C-C5D4A57CE164}']
    ...
    function createDatabaseObjectManager: TObjectManager;
    ...
  end;
 
implementation
 
end.
unit Core.Database.Aurelius;
 
interface
 
...
 
implementation
 
uses
  ...
  Aurelius.Engine.ObjectManager;
 
type
  TAurelius = class (TInterfacedObject, IAurelius)
  private
    ...
    function createDatabaseObjectManager: TObjectManager;
  public
    ...
  end;
 
{ TAurelius }
...
function TAurelius.createDatabaseObjectManager: TObjectManager; 
begin 
  result:=TObjectManager.Create(fDatabaseConnection); 
end;

When I started reading and experimenting with Aurelius, I found the concept of object manager easy to grasp. Admittedly, I had misunderstood it and this made me reach dead-ends several times. The manual describes the concept and mentions that the object manager is not meant to be long-lived like the database connection object or the database manager. Unfortunately, the manual doesn’t give much to figure out how “short” the life of the object manager is meant to be.

After several emails with the TMS people and a couple of trial-and-error attempts, I came to understand that you should keep the object manager alive for as long as you need it to complete all the database related tasks for records and entries that are likely to be reused several times under a specific operation in your application.

To decipher the above (:-)) and apply it to our application, when the user arrives at the main screen, the application (a) retrieves the list of the customers and (b) gets the details for the customer who is selected in the listbox. These operations require an object manager under Aurelius. Based on the above, both (a) and (b) should be done using the same object manager instead of creating different instances for each part. One small note here; in our case we could use different object manager for (a) and (b) but the Photo field (TBlob) is lazy-loaded, which complicates things a little. One object manager, though, solves this challenge.

We’ve set out the basics for our implementation. Now, head towards Part 2 of this post.

Share This:

10 comments

        1. How would that be different from what I do in the post already? What would be the challenge? And from what perspective? Aurelius or MVVM?

  1. Good article
    Please write how work master detail example
    Orders Customers

    And how work with transactions commit and rollback etc

  2. Nice article, thanks!
    Whats this TDicDictionary and TCustomersTableDictionary all about?

    Regards.

    1. Thanks a lot.

      I use TMS Data Modeler to create the unit with the Entities. DM adds the dictionaries you see (although there is an option to disable this).

      I asked Wagner Landgraf (the developer of Aurelius) about this and here is what he says:

      They are not necessary. They are just a structure way to define string constants, so when creating queries with Aurelius, instead of using the name of property ‘Name’, user can type Dic.Customer.Name.

Leave a Reply

Your email address will not be published. Required fields are marked *