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

In the first part, we developed the appropriate design for our application and the relevant classes to allow the use of Aurelius. In this part, we develop the main and customer screens.

The Main Screen

The Model

The model of the main screen brings in the connection with the database via Aurelius and allows the classic operations to be performed: the retrieval of the list of customers, the addition of a new customer, the update and the deletion of an existing customer.

unit Model.Main.Types;
 
interface
 
uses
  System.Generics.Collections, 
  Core.Database.Entities;
 
type
  IModelMain = interface
    ['{3B6359FE-F45F-4E6C-8B06-5322BF7F442E}']
    procedure getCustomerList(var aList: TObjectList);
    procedure addCustomer (var aCustomer: TCustomers);
    procedure updateCustomer (var aCustomer: TCustomers);
    procedure deleteCustomer (var aCustomer: TCustomers);
    procedure addOrUpdate (var aCustomer: TCustomers);
    procedure getCustomer (const aID: integer; var aCustomer: TCustomers);
  end;
 
implementation
 
end.

If you check Figure 1, you can see that we need a list with the customers. Whenever the user clicks on an entry in the list, the details show up in the right-hand panel. The implementation of the Main Model is shown in the next unit.

unit Model.Main;
 
interface
 
uses
  Core.Database.Aurelius.Types, 
  Model.Main.Types;
 
function createModelMain (const aDatabase: IAurelius): IModelMain;
 
implementation
 
uses
  System.Generics.Collections,
  Core.Database.Entities, 
  Aurelius.Engine.ObjectManager, 
  System.SysUtils,
  Aurelius.Types.Blob;
 
type
  TModelMain = class (TInterfacedObject, IModelMain)
  private
    fDatabase: IAurelius;
    fObjectManager: TObjectManager;
    procedure getCustomerList(var aList: TObjectList);
    procedure addCustomer (var aCustomer: TCustomers);
    procedure updateCustomer (var aCustomer: TCustomers);
    procedure deleteCustomer (var aCustomer: TCustomers);
    procedure addOrUpdate (var aCustomer: TCustomers);
    procedure getCustomer (const aID: integer; var aCustomer: TCustomers);
  public
    constructor Create (const aDatabase: IAurelius);
    destructor Destroy; override;
  end;
 
 
{ TModelMain }
 
procedure TModelMain.addCustomer(var aCustomer: TCustomers);
begin
  if Assigned(aCustomer) then
  begin
    fObjectManager.Save(aCustomer);
    fObjectManager.Flush;
  end;
end;
 
procedure TModelMain.addOrUpdate(var aCustomer: TCustomers); 
begin 
  if Assigned(aCustomer) then 
  begin 
    fObjectManager.SaveOrUpdate(aCustomer); 
    fObjectManager.Flush; 
  end; 
end;
 
constructor TModelMain.Create(const aDatabase: IAurelius);
begin
  inherited Create;
  if not Assigned(aDatabase) then
    raise Exception.Create('The Database is nil. Please pass a valid IAurelius database');
  fDatabase:=aDatabase;
  fObjectManager:=fDatabase.createDatabaseObjectManager;
end;
 
procedure TModelMain.deleteCustomer(var aCustomer: TCustomers);
begin
  if Assigned(aCustomer) then
    fObjectManager.Remove(aCustomer);
end;
 
destructor TModelMain.Destroy;
begin
  fObjectManager.Free;
  inherited;
end;
 
procedure TModelMain.getCustomer(const aID: integer; var aCustomer: TCustomers);
begin
  aCustomer:=fObjectManager.Find(aId);
end;
 
procedure TModelMain.getCustomerList(var aList: TObjectList);
begin
  fObjectManager.Clear;
  aList:=fObjectManager.Find.OrderBy('LastName').List;
end;
 
procedure TModelMain.updateCustomer(var aCustomer: TCustomers);
begin
  if Assigned(aCustomer) then
  begin
    fObjectManager.Update(aCustomer);
    fObjectManager.Flush;
  end;
end;
 
function createModelMain (const aDatabase: IAurelius): IModelMain;
begin
  Result:=TModelMain.Create(aDatabase);
end;
 
end.

Note that I do not check for exceptions in the model. This is a personal preference rather than a rule. I prefer to catch any exceptions in the ViewModel because then I can deal with graphical representation if I choose so by pushing messages to the View.

This is pretty much what happens in the Model and how Aurelius is used. Most of the commands can be found in the manual and they are used in their simplest form. In this form, the Model class is, easily, testable.

We have, almost, finished with Aurelius. For the rest of the code which deals with the ViewModel and the View of the Main screen, we don’t have to go back to low-level Aurelius operations. From now on, we don’t deal with Aurelius itself but with the Main Model.

The ViewModel

The ViewModel in the MVVM paradigm is the place where you can transform your data to fit to the View. In our example, we use the ViewModel to retrieve the customer list and customer details from the model and convert this information to a form that the View can digest and use. I don’t show the details of the ViewModel and the implementation that evolve around MVVM. You can check the source code.

We have defined the class to represent the table in our database (TCustomers). I use this same class to share data between the Model and the ViewModel. This point may seem confusing if you have read a few articles about MVVM and how the pattern relates to databases. The Model is described as the part of the MVVM design which manipulates objects that represent business logic and persistence. This is correct but, many times, authors give the impression that the model is the only part that is allowed to use such objects.  This is a common misconception. There is nothing to stop you from using objects and entities that exist in all the parts of the pattern. In our example, his means that you can have a TCustomers object transversing through all the parts of the pattern (Figure 1)

Data can exist in every layer. The contextualisation of data and the conversion to information is the process which depends on the parts of the pattern. I will not expand more on this as it is out of the scope of this article; you can find a more detailed discussion of this in my book.

mvvmanddata

Figure 1: Data and Information across the MVVM layers

To demonstrate the above point, here is the implementation of TViewModelMain.getCustomer, which retrieves the details of a customer from the ViewModel in a form that the View can use.

unit ViewModel.Main;
 
interface
 
uses
  ..
  Model.Main.Types;
 
...
implementation
 
uses
  System.Classes, 
  Core.Database.Entities, 
  System.SysUtils,
  System.Generics.Collections,
  Aurelius.Types.Blob, 
  FMX.Graphics, 
  ViewModel.Types, 
  Core.Helpers;
 
type
  TViewModelMain = class (TInterfacedObject, IViewModelMain)
  private
    ...
    procedure getCustomer (const aID: Integer; var aCustomer: TCustomerTransientRecord);
    ... 
  end;
 
{ TViewModelMain }
 
...
procedure TViewModelMain.getCustomer (const aID: Integer; var aCustomer: TCustomerTransientRecord);
var
  tmpCustomer: TCustomers;
begin
  ...
  try
    fModel.getCustomer(aID, tmpCustomer);
    ...
    if Assigned(tmpCustomer) then
    begin
      if tmpCustomer.Firstname.HasValue then
        aCustomer.FirstName:=Trim(tmpCustomer.Firstname.Value)
      else
        aCustomer.FirstName:='';
      if tmpCustomer.Lastname.HasValue then
        aCustomer.LastName:=Trim(tmpCustomer.Lastname.Value)
      else
        aCustomer.LastName:='';
 
      if not tmpCustomer.Photo.IsNull then
      begin
        aCustomer.PhotoBitmap:=TBitmap.Create;
        Blob2bitmap(tmpCustomer.Photo, aCustomer.PhotoBitmap);
      end;
 
      ...
    end
    ...
  except
    raise;
  end;
end;

The FirstName and LastName fields in TCustomers entity are declared as Nullable and the check to determine whether there is any value to retrieve is required. In the code above we convert TBlob to TBitmap with a call to Blob2Bitmap in Core.Helpers.pas unit.

One important point to note is that I do not free tmpCustomer. The call to fModel.getCustomer attached tmpCustomer to the object manager and, from that point onwards, the object manager takes care of the life time of the object.

The View

The View of the Main Screen does not have any interest regarding the use of Aurelius simply because it does not deal with it at all.

The Customer Screen

The Model

For simplicity, in the application I use the same model with the main screen (Model.Main.pas).

The ViewModel

In relation to Aurelius, the ViewModel doesn’t do anything we haven’t seen in the main screen except in the procedure where the new or updated customer details are passed to the model and saved to the database.

unit ViewModel.AddEditCustomer;
 
interface
 
uses
  Model.Main.Types, 
  ViewModel.AddEditCustomer.Types;
 
...
implementation
 
...
type
  TViewModelAddEditCustomer = class (TInterfacedObject, IViewModelAddEditCustomer)
  private
    ..
    procedure saveCustomer (var aCustomer: TCustomerTransientRecord);
  public
    ...
  end;
 
{ TViewModelAddEditCustomer }
 
...
procedure TViewModelAddEditCustomer.saveCustomer(
  var aCustomer: TCustomerTransientRecord);
var
  tmpCustomer: TCustomers;
begin
  if aCustomer.ID=0 then
    tmpCustomer:=TCustomers.Create
  else
  begin
    try
      fModel.getCustomer(aCustomer.ID, tmpCustomer);
    except
      raise;
    end;
  end;
  tmpCustomer.Firstname:=aCustomer.FirstName;
  tmpCustomer.Lastname:=aCustomer.LastName;
  if Assigned(aCustomer.PhotoBitmap) then
    Bitmap2BlobAs(aCustomer.PhotoBitmap, tmpCustomer.Photo, 'png');
  try
    if aCustomer.ID=0 then
      fModel.addCustomer(tmpCustomer)
    else
      fModel.updateCustomer(tmpCustomer);
 
    //Or just call this instead of the above if-else block
    //  fmodel.addOrUpdate(tmpCustomer);
  except
    raise;
  end;
end;
 
...
end.

Aurelius determines whether an entity which is controlled by the object manager is saved in the database by, simply, checking the primary key. A value of 0 means the record does not exist in the database or not loaded in the object manager; other values in the primary key signal otherwise. I use this trick above to determine which procedure must be called (addCustomer or updateCustomer). Alternatively, the SaveOrUpdate method from the object manager can simplify things.

The View

As before, the View of the customer screen has nothing to do with Aurelius.

Source Code

You can download all the files used in this post from here.

Special Thanks

Many thanks to Wagner Landgraf from the TMS team who proved very patient and took the time to answer all my emails.

Suggestions, Improvements, Omissions, Mistakes

If you have suggestions and ideas how to improve this article or have spotted an error, please leave a comment or contact me. Thanks.

Share This:

8 comments

  1. Generally speaking, I”m afraid that the MVVM way of coding doesn”t meet the “keep it simple” philosophy permitted by a Delphi “Rapid Application Developpement” : simply put components on a form and connect them to data.

    Please John, forgive me but from my little point of view, i found that the purposed sample shows “how to make thing complex when you can make them easier”… The VIEW connection is awfull: you have to list in a record all the visible controls in order to enanble/disable them according to the state of data operation! A simple TDataSource connected to a TDataSet just do it fine, isn’it? Why do we have to reinvent the wheel?

    In the same time i think it”s important to decouple code (logic/UI) for many of the goods reasons exposed (test, logic, reusability, platform translation, sharing..).

    So i will recommand, when creating a new project, to begin to write the “core logic” in a consol application. Why not create in this phase a global object/component that will gather the main objects, procedures and functions (primitives). TMS AURELIUS is good at this level!

    In a second time, decide the UI platform VCL/FIREMONKEY/DLL . TMS Aurelius is (very) good at this level too with its DataSet possibility (i.e coupling data with UI elements with some DataBinding or TDBEdit and so on…)

    I”ve not clearly understood associations after 3 years using TMS AURELIUS. I use TMS Data Modeler to get the right coding but don”t know precisely how it works…

    1. Let me clarify something which may not be apparent at first place in the posts; this example attempts to show how you can use Aurelius in a MVVM design. So, the focus is not on ideal MVVM design but how you can isolate Aurelius and, still, be able to move back and forth in the specific design paradigm information which is governed and determined by the framework.

      Having said this, please see a few thoughts on your comments:

      Generally speaking, I”m afraid that the MVVM way of coding doesn”t meet the “keep it simple” philosophy permitted by a Delphi “Rapid Application Developpement” : simply put components on a form and connect them to data.
      Please John, forgive me but from my little point of view, i found that the purposed sample shows “how to make thing complex when you can make them easier”…

      This is a very simple example and, arguably, separating the components can introduce more coding, properties, etc. The value of such separation is coming into play with bigger projects. Anyway, this is a very common criticism and debate among MVVM zealots and sceptics.

      The VIEW connection is awfull: you have to list in a record all the visible controls in order to enable/disable them according to the state of data operation! A simple TDataSource connected to a TDataSet just do it fine, isn’it? Why do we have to reinvent the wheel?

      As said above, it is a simple example. There are more innovative ways to do all this housekeeping but I, just, used properties for the sake of the example as the focus is more on Aurelius. Moreover, in the approach here, I don’t use any graphical data bindings (LiveBindings, etc.)

      In the same time i think it”s important to decouple code (logic/UI) for many of the goods reasons exposed (test, logic, reusability, platform translation, sharing..).

      Exactly; but this berries some associated cost and your observation about the properties is one such a cost.

      So i will recommend, when creating a new project, to begin to write the “core logic” in a console application.

      Just take the model(s) and put them in a console.

      Why not create in this phase a global object/component that will gather the main objects, procedures and functions (primitives). TMS AURELIUS is good at this level!

      What are the “main objects” you are referring to? The model(s)?

      In a second time, decide the UI platform VCL/FIREMONKEY/DLL.

      I have; this is a FireMonkey application.

      TMS Aurelius is (very) good at this level too with its DataSet possibility (i.e coupling data with UI elements with some DataBinding or TDBEdit and so on…)

      Yes; with TAurliusDataSet. But, again, in an isolated design, the View should never know about the whereabouts of Aurelius. What a View knows is how to retrieve information from the ViewModel, which, in turn, represents data and graphical states.

      I”ve not clearly understood associations after 3 years using TMS AURELIUS. I use TMS Data Modeler to get the right coding but don”t know precisely how it works…

      I use TMS Data Modeler as well but for a different reason. 🙂

      1. “Merci” for your return John.
        I’m developping since 1984 beginning with Macintosh ASM 6800, Think Pascal and Think C++: I had the time to see a lot of modes in programming…. It’s hard to convince old apes like me who have taken bad habits. But there is always something good to take new theoretical steps, especially when the concepts are well explained as you do.
        Désolé pour mon anglais!

        1. Thanks Didier.

          I can understand your point about the challenges to change. I have found myself in this situation in the past.

          But, as you mention, as well, theoretical discussions and new approaches are always welcome. This is how things move in the long run.

Leave a Reply

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