Wednesday, October 3, 2018

Pattern, naming and MVVM from a Delphi point of view.

Patterns are good tools and if you name it correctly, other developers should know what you are talking about.

Name it singleton and every developer knows how to deal with it... (I hope). If you are talking about dependency injection, perhaps you are not using it, but at least you can google it. So giving things the right name is always a good thing.

So let's take a look at the development of a new FMX application. Of course, you want modern design, maintainable code and testable business-logic. I think you are looking for the MVVM pattern. Model, View, ViewModel, but after File, New, Multi-device Application you have a project and a Mainform and with a closer look into your tool palette, you are unable to find a View, a Model or even a ViewModel. Nothing to put on your form.

BTW.: Clicking non-visual controls on your form is the worst thing you can do with the goal of MVVM.

And now? What to do? Google for a library?. Perhaps you find something and using a library is normally better than coding all by your self. Do you remember the famous words?

Using is faster than coding!

There are many useful libraries out there! Some are free, some are expensive and some are necessary. I'll never start a new project without my FDK. ;-) Especially when dealing with FMX. So every new project! My last VCL project/tool was with XE. 

Perhaps you are already using my FDK, but unfortunately my MVVM-Plugin is still not available today. Why? Because I'm still dealing with different MVVM-pattern-designs.
After "MVVM was the start" in 2015, "MVVM or what I think it is" in 2016, trying attributes in 2017, "MVVM 2.0 I did it my way" in April 2018 and some kind of different Bindings (no blog post - let's call it MVVM 2.5) -  I'm still working on the "best" way to do it right. I thought I was "short on final" with my View to CRUD-DB Binding pattern until last month "Sir Rufo" came up with a new binding approach. This with my last attempt will be the final one - I think - today... ;-)

At the moment I have 10 different Apps with my MVVM-Stuff from 2015,2016,2017 and 2018 and of course no MVVM-library form each year is compatible with the others. 4 different Libraries that are used in - at least - 2 Apps... Very bad to maintain. But back to Naming...

We are Delphi developers and I never had a View. I have forms, frames - perhaps I use panels or layouts to do visual tricks. Or on iOS & Android I use TabControls to display different - ok call it views. But why name it View? It's a Form... Because a Form is not a View and a Form could display many View's, but this View's are Layouts or Frames? Confusing... Starting in 2015 I had no clue why a Form is not a View - now I understand, but perhaps I will change the names... ;-)

Let's do some examples.

You have:

- TPerson with Name, Day of birth and more
- TAddress with Street, Zip, Town...
- TCommunication with Fon, Mail and more
- TBank with Bankname and Account
- TInvoice with Invoice Fields
- TArticle with Title, Price, weight

In 99,9% all Visual-Controls on my Forms representing a Field in the Database. The Database has - of course - more fields like Timestamp, CreationDate, LockOwner and more.

Perhaps my DB_Table (Person) is a TPerson,TAddress,TCommunication,TBank in one table. 80%

Perhaps I have TPerson, TAddress1, TAddress2, TFon1, TFon2, TBank1, TBank2, TBank3 100% until 2010 in one record written as binary to file with index. *lol*

Today I would have one DB-Table for TPerson, one for TAddress, one for TCommuniction  and one for TBank.

For this example I like to display this kind of data directly in Form-Fields (TEdit,TCombobox, TCheckBox etc.) and not in a Grid. Perhaps at the bottom a grid with the TInvoice and on dblclick display the Invoice on a second form with the containing TArticle(s).

So what do we have?

A FORM with Field separated in 5 subFRAMES, each subframe depends on a Databasetable. To Display more than one TAddress I had to create the controls at runtime on a scollbox. 
F for Form or Frame and D for Database.

Rule of thumb: Never use visual controls to keep/store your Data. 

That's why we need some class/Memory for our Fields. (That's why I would never use a Live-DB-Connection, where edits could only display the data if you have an open connection.) ARC on iOS & Android and no ARC on Windows - so we need an InterfacedObject as Memory - representation of our Form / Table. One last thing is something to Control the Dataflow from From (TEdit) to Memory -> to Database. Do we have a new Name?

FCMD - Form,Controller,Memory,Database.

FCMD - Sound like my new MVVM... ;-) or is it MVC?

Btw: What was the reason to do MVVM? Ahh yes... to have a good layer separation and testable units with - at the best - no dependencies.

Because we all doing unit-test... ;-)

What is your unit test coverage? 3%, 30%, 70% more?

OK - assume 100% coverage of your business units. Do you have unit tests for your ViewModel?
Maybe some time we got an IDE that could display the unit test coverage.

Back to the Form - we have a grid with a list of invoices - the grid has it's own model inside. Each row could be a ViewModel - InterfacedObject as Memory-Representation of the Database. So we need a Grid-Controller for displaying the TArray<IInvoice> perhaps with paging and background thread reloading new rows... Again a Controller to move n rows from Memory to the Grid-(View)Model.
And a Model "InterfacedObject Multiloader" to load all Invoices with
"Select * from InvoiceDB where Person = Person.GUID". 

On a DBLClick we could create a new Form and inject the IInvoice from GridHelper(ARow).GetRow<IInvoice> into the Controller of the new Form. Bingo... Working Demo.

So why FCMD and not MVVM or is FCMD another name for MVC?

To answer the question we have to look at the bindings.

In MVC the user uses the Controller to manipulate the Model that updates the View.
In MVVM then View has a bidirectional binding to the ViewModel and the ViewModel has a bidirectional binding to the Model. Is the Model the Database - no. Where is the data stored? In the ViewModel or in the Model? Ask 10 developers and you got 10 different answers. My ViewModels also have the Data and the Model has "all-Data" or the Interface for storing the Data via DB or REST or SOAP or Whatever.
In FCMD you have the Form and the RAD-approach. With the assignment of the ViewModel Memory-Representation to the View Form, you have to create your bindings to the Memory-Representation. This creates a Bind-Controller. If you type something into a TEdit the Bind-Controller transfers the data directly into your Model TInterfacedObject. This Object fires a propertychanged over the ViewModel  Bind-Controller direct to the view - Form. If you reload or load a different data, your View Form also displays the new content. The Memory Object has all Fields of the TableRow with some Attributes to Create and Handle the Database with CRUD I/O.
Perhaps we change from C for Controller to B for Binding.
So with FBMDR you could write your data to a local DB and in Background over REST to a Server. That's why my CRUD/ORM Memory-Representation could transfer all the fields to and from JSON.
Btw. Form-Bind-Memory-Database sounds like an in-memory database. So change the D to C like in CRUDTable

At this point no unit tests for the View Form-Logic, because there is no ViewModel. Perhaps parts of the ViewModel stuff is in the MemoryObject like "CanSave/CanDelete/Modified" and so on. The other things could be sent via the Bind-Controller or with TMessages into the View Form. Like DisableButton. 

With FBMC(R) 99,9% is testable like in MVVM but 70% less source code to type and 60% fewer Units.

I would love to see FBMC spread all over the Delphi-Community and beyond,

#FBMC - Form-Bind(ed)-Memory-CRUDTable


or perhaps I do some modifications and call it MVVM 2.5 - again...


The Binding-Controller not only handles the transfer but "he" is also able to do some data conversion, like string to integer or String to TDatetime or A->B, B->A.

So far so good. But with our new FMX-App, we need more. Nearly every FMX-Control has some animations build into the style. This animation is handled with timers - of course in the main thread. Like MouseDown, MouseOver, MouseUp and so on. If you are new to FMX you would write:

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  DoSomeStuffFor20Sec;
  Button1.Enabled := true;
end;

If you click on Button1 the button is still enabled- you get no MouseDown animation your UI has no response and after 20Sec you can - perhaps - see MouseDown, MouseUp. But things are getting worse.

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  NewForm := TNewForm.Create(Application);
end;

If you free the Form that contains the Button1 in the TNewForm Show or "Create Event". You'll get an exception because the MouseUp-Animation will be displayed after the TNewForm.Create is called on a no more assigned Button1.

Nearly everything you want to do like the good old RAD-Days you have to do asynchronously. At best, in the onIdle-Event for UI-Stuff or in Thread with other things.

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  Button1.Text    := 'Processing...';

  TAsync.Await(Procedure
    begin // Thread Context
      DoSomeStuffFor20Sec;
    end).
  Execute(Procedure
    begin  
      Button1.Enabled := true;
      Button1.Text    := 'Save'; 
    end);
end;

OR

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  Button1.Text    := 'Processing...';

  TIdleWorker.Execute(Procedure
    begin // UI Thread 
      DoSomeStuffWithUI;
      Button1.Enabled := true;
      Button1.Text    := 'Save'; 
    end);
end;

With the Bind-Controller from #FBMC you could bind a command/Method at the Button that can handle this kind of action without the need of writing additional lines of code.

With all these and the additional JSON Import/Export, REST Client/Server Unit you are able to Build your Multi-Device-App with "Cloud-Dataexchange" in minutes. At the moment it works perfectly with my Windows-Server. 

The next step is to test the Serverside in a Linux-Docker-Container...

But every day is too short... So stay tuned...

No comments:

Post a Comment