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.
Contents
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<TCustomers>(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.
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. T
he 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.
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…
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:
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.
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.)
Exactly; but this berries some associated cost and your observation about the properties is one such a cost.
Just take the model(s) and put them in a console.
What are the “main objects” you are referring to? The model(s)?
I have; this is a FireMonkey application.
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 use TMS Data Modeler as well but for a different reason. 🙂
“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!
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.
Didier – I also started programming 1983 on a Motorola 6800 D2 development kit. I think we have the same bad habits to break 🙂
Regards,
Dave
Tank´s
Very Well
I want more samples and thecnics;
But great sample
what kind of examples would you like to see? Anything is particular?
I reading your book,
I am studing
Thanks John.
I am looking for MVP and MVVM in delphi for some days, your book and papers on MVVM in Delphi and Aurelius are fantastic, and I try to transfer your MVVM code to uniGUI Framework and it works well.
Good jobs.
Thanks for the comment.
Let me know if you need any assistance. I am more than happy to help.
Hello John, Congratulations on your explanation, but would you like to make a small example without using MVC, just to know how to create a firebird database using firedac with TMS Aurelius?
Hi,
thanks for the comment.
I haven’t tried to use Aurelius with FireBird. Have you made any attempts?
Very simple to build João!
just take the example that comes with the Aurelius TMS and change the bank that will be running.
Are You shure that this code is correct?
procedure TModelMain.getCustomer(const aID: integer; var aCustomer: TCustomers);
begin
aCustomer:=fObjectManager.Find(aId);
end;
I think it should be like that
procedure TModelMain.getCustomer(const aID: integer; var aCustomer: TCustomers);
begin
aCustomer:=fObjectManager.Find(aId);
end;
and similary when You use “Find” method.
Sorry, what’s the difference?
The difference is that the ObjectManager should be know what kind of object do You expect as a result of Find method. You usually have more that one entity in the database and finally many classes in code. As You know in the bracket is a value of the primary key but which class (entity)?
Ups 🙂 I make mistake in the sugested code 🙂 should be like that
procedure TModelMain.getCustomer(const aID: integer; var aCustomer: TCustomers);
begin
aCustomer:=fObjectManager.Find(aId);
end;
Hi Piotr, in your code as well 🙂
it looks like you miss the
You are right…the generic type is required.
Funny enough, if you look at the original code on github, the statement is correct.
Thanks for pointing it out to me. I have now corrected the code in the post.
Hi, have send two times the same code with the class name … I think that the name of the class could be interpreter by mistake as a HTML tag in this why is removed :).
All the best 🙂
I would put
TAureliusDataSet
in the View (Form) and I would provide the Criteria or Source List from the ViewModel.If
adsCustomers
is theTAureliusDataSet
in the View, then I would write something like this:adsCustomer.SetSourceList(fViewModel.getFullCustomerList)
Not sure I understand what you mean here; if you have the dataset in the View (Form), it will be freed normally. If you put it in the ViewModel, you will create it with the owner to be
nil
and, then, you need to free it manually.Great article just one small question. How do you deal with null values when you connect to non-data-aware controls?
Thanks.
What do you mean to “non-data-aware” controls? And do you mean programmatically or using TAureliusDataSet?
Can you provide an example to illustrate the problem with null values?
Hi John,
Never mind John, I had some brain fart 🙁
Hi John
Firstly, thankyou for taking the time to create this content – I am just in the process of learning MVVM and as an Aurelius user I have found this to be very useful.
One other thing I am wanting to learn is how to do TDD, and I noted as I was working through your examples the use of interfaces and also the comment that the “Model class is, easily, testable”. I’d be really interested to know just what you would test in the model class, and an example of how to unit test one of the methods would really enhance my understanding of how to use all three technologies (Aurelius, MVVM and TDD).
Hi Shane,
Apologies, I missed your comment. Thanks for the kind words.
About testing the model, I would create a descendant of
IAurelius
which links to an in-memory database and pass it toTModel
constructor. After that, the interface methods can be testedHi John,
Thanks for the pointer re TDD – Once I have mastered MVVM, I’ll be sure to apply that approach.
Another question if I may, this time about the point you make re the concept of data existing in every layer. I’ve read your book and been working through creating my own MVVM test application and it seems entirely logical to me that you would use the entity classes (e.g. TCustomers) in every layer, yet in your examples, you seem to do a lot of converting between records and objects and I’m not sure why. The case above for example you use a TCustomers object to populate a Transient Record which is then a property of the IViewModelAddEditCustomer Interface.
Is there some reason why the TCustomers object couldn’t be the property of the interface, and have the view directly update the properties of that object? I don’t know much about interfaces, but I’m wondering if it’s because of a limitation of Interfaces or specifically a MVVM reason?
If it’s best to use Transient Records for this purpose, I’m thinking that perhaps having an “Assign” like method in the TCustomers (and it’s temporary record partner) class would make some sense – any thoughts on the MVVM validness of that approach?
Hi Shane,
You can use
TCustomers
anywhere and in any layer you want and, yes, it can be a property as you suggest. For the purists, it would be absolutely consistent with the MVVM approach. The only reason I have the transient record in this example is to show that the view model works as a layer in between the model and the view where you can manipulate data before pushing it to the view. You may wish to add more information, perform checks, trigger actions, manipulate what should be shown to the view. All these are things that may not necessarily be done inTCustomer
, in the model or in the view.I hope this makes sense
Thanks John – that all makes sense. I was wondering if I should include validation etc. in my version of the Tcustomers class or allow the Viewmodel to register as an observer of that object to get notified of changes so it could then process similarly to your example. Guess there is no real right or wrong way to implement this. CheerS.
Hi Shane,
I think it has more to do with the structure of the classes and the code and what sort of validation we are talking about. The last version of Aurelius introduced the ability to validate properties and this is done very close to the database level (just before the changes hit the database).
Other types of validations or checks may work as decision points in the whole flow of an application. I can think of a situation where you need to approve an order of an item or a service. You would need to combine data from the customer database, the inventory, the details of the order, credit checks, payment completion, etc. Under MVVM paradigm, all these seem like separate Models and the checks can be done in the ViewModel as it can host several Models. And, if you err on the side of purists in OOP, then you may have separate classes for readers and writers of all these models. ViewModel seems like a space where you can manage all these under one roof.
Another type of checks you may find confusing is exceptions. Most of the exceptions need some form of visual notification or acknowledgement that involves a “view”. My personal preference on this is to check for exceptions in the ViewModel and notify the View. This can also take place in the Model but you then need to somehow pass the notification from the ViewModel to the View to show a message. I found this becoming cumbersome and it involves a lot of boilerplate code without real benefits.
So, as you say, there is not a strict academic right way to do this. 😁😁😁