Tuesday, March 16, 2021

Unittests in Bold. Is it possible ?

 I started to explore interfaces. One of the reasons I want to know more about interfaces is that I want to use unittests. Interfaces are a key part that makes it easier to make the code more testable.
I worked with Delphi for a long time and I know the concept behind interfaces but never really created new ones and used them. I believe one reason for this is that Bold generates code for classes in the model in one file businessclasses.pas. businessclasses.pas is a hotspot. It must be included whenever you want to reference a class or member in the model.

The structure of this big file is this:

(*****************************************)
(*      This file is autogenerated       *)
(*   Any manual changes will be LOST!    *)(*****************************************)
(* Generated 09.02.2021 17:51:02         *)(*****************************************)
(* This file should be stored in the     *)
(* same directory as the form/datamodule *)
(* with the corresponding model          *)(*****************************************)

unit BusinessClasses;

{$INCLUDE BusinessClasses_Interface.inc}

{$INCLUDE AmObject.inc}
{$INCLUDE Quantity.inc}
// and 80 other include files

procedure TBusinessClassesRootList.Add(NewObject: TBusinessClassesRoot);
begin
  if Assigned(NewObject) then
    AddElement(NewObject);
end;

function TBusinessClassesRootList.IndexOf(anObject: TBusinessClassesRoot): Integer;
begin
  result := IndexOfElement(anObject);
end;

// And example of code for class TPlanMission in model.
// The link TPlanMission.StateInProcess can be read and changed
// The model contains thousands of these

function TPlanMission._Get_M_stateInProcess: TBoldObjectReference;
begin
  assert(ValidateMember('TPlanMission', 'stateInProcess', 194, TBoldObjectReference));
  Result := TBoldObjectReference(BoldMembers[194]);
end;

function TPlanMission._GetstateInProcess: TVehiclePool;
begin
  Result := TVehiclePool(M_stateInProcess.BoldObject);
  if (assigned(Result) and not (Result is TVehiclePool)) then
    Assert(false, SysUtils.format(BoldMemberAssertInvalidObjectType, [ClassName, 'stateInProcess', Result.ClassName, 'TVehiclePool']));
end;

procedure TPlanMission._SetstateInProcess(const value: TVehiclePool);
begin
  M_stateInProcess.BoldObject := value;
end;


BusinessClasses_Interface.inc contains actual class definitions.

interface

uses
  AttracsAttributes,
  BoldAttributes,
  Classes,
  SysUtils;

type
  TBusinessClassesRoot = class;
  TBusinessClassesRootList = class;
  TAmObject = class;
  TAmObjectList = class;

// About 900 more forward declarations of classes

  TBusinessClassesRoot = class(TBoldObject)
  private
  protected
  public
  end;

// To make it easier to read I only left Created property in AmObject and a override method in Bold CompleteOverride

  TAmObject = class(TBusinessClassesRoot)
  private
    function _Get_M_Created: TBADateTime;
    function _GetCreated: TDateTime;
    procedure _SetCreated(const NewValue: TDateTime);
  protected
    procedure CompleteCreate; override;
  public
    property M_Created: TBADateTime read _Get_M_Created;
    property Created: TDateTime read _GetCreated write _SetCreated;
  end;

// Definitions of other 479 classes follows here

implementation

uses
  {$INCLUDE Attracs_Implementation_Uses.inc} ,

So the result can be big, really big.
Attracs have 480 classes. 
BusinessClasses.pas has 88902 lines
BusinessClasses_Interface.inc has 37480 lines
Attracs_Implementation_Uses.inc has only 90 lines

Those files are generated from the model. Only inc-files like AmObject.inc, Quantuty.inc, etc with the actual code for methods can be manually edited.

So now when we know about the structure the BIG question is. How can I make unit tests for classes in businessclasses.pas ? This is the main reason I wrote this post to get some input from other developers.
I think the main problem with businessclasses.pas is the size and it is generated code.
Generally, a developer wants to split it so each unit only contains one or a couple of classes. These make the code more flexible. Attracs_Implementation_Uses.inc also include other units not related to model.

Lets take an example. Attracs_Implementation_Uses.inc contains ParcelTrackingHandling.

This is the unit

unit ParcelTrackingHandling;

interface

uses
  Classes,
  SysUtils,
  BusinessClasses,
  BusinessInterfaces;

type
  TMediaHandle = class(TInterfacedObject)
  private
    fSendmedias: TStringList;
    function SendLinkTo(aContactMedia: TContactMedia; aParcel: TParcel): Boolean;
    function SendMail(const aEmailAddress: string; aParcel: TParcel): Boolean;
    function SendSms(const aMobileNumber: string; aParcel: TParcel): Boolean;
    function ExternalLinkPossibleToSend(aParcel: TParcel): Boolean;
    function GetTrackingSubject(aParcel: TParcel): string;
    procedure MayAddRole(aResults: TStringList; aContactMedia: TContactMedia);
  public
    constructor Create;
    destructor Destroy; override;
    function GetReceivingParty(aParcel: TParcel): TDepartment;
    function SendExternalLink(aParcel: TParcel; aResults: TStringList): Boolean;
  end;

I could make an interface for this in a new unit

unit BusinessInterfaces;

interface

uses
  Classes,
  BusinessClasses;

type
  ITracking = interface
  ['{309FF866-56EC-405D-8168-D03DB8BBB8F2}']
    function GetReceivingParty(aParcel: TParcel): TDepartment;
    function SendExternalLink(aParcel: TParcel; aResults: TStringList = nil): Boolean;
  end;

implementation

end.

But unfortunately this require BusinessClasses in the uses as TParcel that is a class in the model is used as parameter. Any idea how to avoid this chicken and egg situation ?

Input and comments are appreciated 😃

Sunday, February 21, 2021

Bold big steps


Now when Bold framework are open source we have the possibility to decide in what direction we want. Bold for Sydney is next obvious step. So far Daniel Mauric have done most of the development in Attracs version of Bold so far. Both bugfixes, optimizations and new features. For example our version don't require calls to defaultsubscribe in codederived members. This make code more clean and readable.

But with open source we can cooperate as there are many smart developers  in Bolds community. One example is Yuri that solved the problem with x64. There was a runtime exception that was challenging to fix. Thanks Yuri! I have four ideas here for improvements in Bold. I am not sure if they are doable. I am humble 😊

  1. Threads

With Bold you don't need to write SQL to fetch data from database. Bold generate needed SQL and execute it whenever code needs it. We call it lazy fetch. Disadvantage are that it is slow as SQL is executed one by one in serie. Performance can be improved by prefetch data before it is needed. To call TBoldlist.EnsureObjects is one way. We have even an improvement called spanfetch that Bolds author Jan Norden wrote for us 10 years ago. It try to improve performance by collect access for each class before it is needed in derived members. But the generated SQL is still executed in serie in main thread. So the hourglass is common if there is much data to load. I suggesting have a pool of threads and let them execute SQL instead. It has some advantages.

  • Hourglass is gone and main thread is responsive all the time.
  • Better performance as each SQL is executed in own thread in parallell.
  • Possible to cancel current loading. Just terminate threads that executes SQL.

  1. Replace inc-files 

Code from modelled methods are in inc-files. Class definitions are in a separate inc-file. They are then references from businessclasses.pas. It works well but has some disadvantages for a developer. Most tools assume that code is only in pas-files. An example is Codeinsight in Delphi. It works not so good in inc-files. But also other tools that handle code like analysera have problem.

If you load businessclasses.pas Delphi Sydney be prepared to wait. Much slower than previous releases. I suppose it is because the new language server protocol for Codeinsight. It parse the file when loaded on IDE. Our model has about 460 classes so it try to parse the whole model which of course is slow. If classes can be arranged in ordinary pas-files instead.

Advantages:

  • Smaller files don't need to load all at once so LSP don't need to parse so much when load businessclasses.pas.
  • Tools like Codeinsight and code analyzers works better.

This means code generation needs to be changed to generate code to pas-files instead.

  1. Interfaces

Unittesting with Bold isn't easy. If I want to test class A but that have links to class B that have links to class C. We want to test class A in isolation. Best way to achieve that is to use interfaces instead of direct links to other classes. This means code generation need to be changed to generate interfaces for links.


  1. OCL syntax checks

Visual Studio can syntax check LINQ if I understand it correct in compile time. Could we make similar checks with OCL? BoldHandle already have methods to validate OCL. But only in runtime.


https://stackoverflow.com/questions/5681726/how-to-get-compile-time-checking-for-value-types-in-linq-lambdas-expression-tree

Saturday, September 26, 2020

Bold source is available!

Yes, finally Bolds source is available !

To make a long story short, her are Bold for Delphi repository.

The longer story

I begin working with Attracs in autumn 2005. I had no knowledge about Bold or even Delphi. First Bold seems hard to learn and cryptic. But after a while, I saw the beauty in it. Less code that does more. The level of abstraction is increased. This is perfect for complex business logic. It felt strange that it was only available for a few developers because Borland/CodeGear/Embarcadero keep the IP and have no maintenance.

May 2012. Seriously I started the idea to make Bold available for the whole Delphi community at summer. By an accident, my achilles tendon was cut off. I could not work, have to use crutches and I was restless. I thought what a waste of time and intellectual resources with many unofficial forks of the same thing, original Bold. I wanted Bold as open-source, free to everyone to use, inspect, and improve the code. Probably I was naive that I could change the current situation. But I realize that send a single email to Embarcadero wouldn’t change anything. A petition could be a possibility. So I set it up and tried to spread the word. Not much happened. But after a while, ADUG posted a blog about this on DelphiFeeds. And the number of signs increased. A lot of comments revealed the frustration of the situation. It appears that there was a lot more demand for a framework like Bold than I initially guessed.

August 2012. Now I got attention from Embarcadero. After some emails, I and representatives from my employer Attracs had a Skype meeting with Embarcadero. We all agreed on NDA. I was so amazed and happy that they talk about to publish the code. Finally! But Embarcadero was busy to deploy new versions of RAD Studio so 3 versions passed, XE3, XE4 and XE5. Occasionally I have discussions with Embarcadero when they had time.

October 2013. Finally, we arranged a second meeting on Skype with Embarcadero, myself and representatives from Attracs. Unfortunately, the meeting failed. Embarcadero had terms for the license that Attracs could not accept.

January 2014. I had another Skype meeting with Embarcadero. We discussed how to continue as we both have common interests in Bold. But no concrete result from this.

September 2014. Meeting by phone with a representative from a german company. They are stuck with Bold and D2007 like Attracs but unlike Attracs that have no access to the full source. We agreed they will contact a german lawyer to clarify the legal situation. The lawyer got access to the license draft from Embarcadero and our BoldScala license from 2002. A clause was added to the license to protect developers in case Embarcadero change license conditions. The license was inspected by Embarcadero.

When Marco Cantu begins working as a product manager at Embarcadero I begin to email him. We agreed that it makes sense to have Bold as open source. But he thought it would take a long time before it would happen. And he was correct, it took a long time. I have dreamed about this but it is real now! We should send a big THANKS to Marco Cantu for his work to realize this. I don't believe it has happened without him.

But this is just the first step on the journey. There is so much to do.
There is now a Slack space dedicated to Bold for Delphi and how to organize stuff.
If you are interested in Bold I can invite you if you send an email to roland(dot)bengtsson(at)gmail(dot)com.

Current situation and roadmap
  • Current source is for D2006/D2007. There is a missing file due to copyright problem but we work to solving that
  • Attracs Bold version is for Delphi Sydney. We will publish that when som dependencies are removed.
  • Bold have no visual UML editor. There are discussions of the best options.
  • The documentation is a bit old. Not many examples and demos for beginners. If you feel not confidence to change in Bold itself here is a field that welcome more contributions. Maybe a Youtube video "Getting started with Bold for Delphi"!
  • We have some unittests but as always there is much room for improvement.
  • Currently it feels like we try to support D2007 but has focus on later Delphi versions.

Finally I want to thanks to all that signed the petition. Without that I don't think we got the attention from Embarcadero and Bold would still be closed. To ADUG that posted my petition, that step spread the word a lot. And to Embarcadero company that agreed to publish the source to customers.

Regards
Roland Bengtsson

Wednesday, August 19, 2020

Bold will be open source !!!

 Wow, I have never been so excited about a sentence in a blog post.

I started eight years ago trying to convince Embarcadero that open source is the only way to move forward. I created a petition about it https://www.change.org/p/embarcadero-technologies-release-the-intellectual-property-of-bold-for-delphi. Thanks for those that signed it and make attention to Embarcadero. And today I see this blog https://blogs.embarcadero.com/august-2020-gm-blog/

This summer we will also open source the code base of Bold, which is a sort of low code solution for RAD Studio. We have a passionate group of Delphi community experts who will take that effort forward.

We have our Bold source in a private repository and can contribute anytime. It has been in production for 17 years now. We make continuous bugfixes and optimizations in the source. Sometimes also new features. Currently, we are using Delphi 10.3 Rio but will soon switch to 10.4 Sydney.

If you are a Bold developer or maybe just interested in model-driven development you can send a message to me at  roland.bengtsson(at)attracs.com with questions or thoughts. But the first and most important move, to wait for Embarcadero publish to the source 

Roland Bengtsson


Saturday, February 24, 2018

Validation of data

Sometimes when I analyze data in database I discover bugs.
For example if a class/table TripEvent contains 2 date attributes ArriveDate and CompleteDate.
Of course ArriveDate is always before CompleteDate. But for some reason the order is wrong in database. There is logic in code that decide the dates but I have no idea how it can happen.

I can add validation of data before it is persisted to database. The method TBoldObject.PrepareUpdate is virtual and is called just before the object is persisted. This is a good place to validate data. The business rule:

ArriveDate <= CompleteDate

ArriveDate should always be less or equal to CompleteDate.
What happens if this rule is violated it is up to us to decide.
In this case I want an exception  so no data is saved to database. This is done by a rollback in the global exceptionhandler. I use Assert as this is a convenient way to validate data. Just check that Assertions is enabled in compiler settings.

procedure TTripEvent.PrepareUpdate;
begin
  inherited;
  if not (M_ArriveDate.IsNull or M_CompleteDate.IsNull) then
    Assert(ArriveDate <= CompleteDate,
      Format('Validation failed as ArriveDate %s > CompleteDate %s', [DateTimeToStr(ArriveDate)DateTimeToStr(CompleteDate)]));
end;

Before validation there must be a nullcheck as there is no point compare null values.
Now if validation failed an exception is raised and that is logged with callstack.
I can now see where in the source it is called and easier fix it.

Sunday, June 11, 2017

True Unit tests in Bold ?

I think you all heard about unit testing. Maybe some of you even practise it?

I recently bought Dependency Injection book by Nick Hodges. It is about to have loose coupled classes. And this is a precondition for true unit-test where each class is tested independently.

This make me thinking. How would that be possible in Bold? An important part in Dependency Injection is interfaces but the model don't support that now. Ex class TPerson have a link to TAdress. There may also be a method like TAdress.AddPerson(aPerson: TPerson).

To test AddPerson with a unit-test one interface is needed IPerson. The signature need to be changed to TAdress.AddPerson(aPerson: IPerson). Now the interface can be used instead. The same is true for any relations between classes. If there is a single link TPerson.homeAdress: TAdress that should be changed to TPerson.homeAdress: IAdress.

Our main Application Attracs have a huge model. Over 400 classes. So to change this some kind of automation is needed. I thought about to scan businessclasses.pas with a program and generate a new file businessclasses_Interfacedef.pas to differentiate with the original businessclasses_interface.pas.

The new file would contain interfaces to classes in businessclasses_interface.pas like IPerson, IAdress etc. The public methods in each class should be added manually as you cannot indtroduce a lot of interfaces in am application in one step. Easier to divide it in smaller steps. So the generation of file businessclasses_Interfacedef.pas preserve existing methods. New interfaces has no methods.

Next step would be to change application code to use interfaces.

Instead of

vPerson.homeAddress as TAddress;

do

vPerson.homeAddress as IAddress;

And I think this is the main issue, a lot of changes is required.
But it can at least be done in smaller steps, class by class.

And the rewards is possibility to use true unit-testing. Test all methods in a class. As interfaces is used in relations and parameters fake instances can be used.


Comments are welcome because all this is completely untested. I just brainstorm here :)

Tuesday, March 7, 2017