Sneak preview about DORM, The Delphi ORM

Design Patterns, Programming, RTTI 18 Comments »

My yesterday post about this busy time, have raised some interest about DORM, the Delphi ORM.

So, also if I still haven’t released any files, wish to expose some internals about DORM.

DORM is an implementation of the DataMapper design pattern written having Hibernate in mind.

It’s completely unit tested and have the following features:

  • External file mapping. (JSON format)
  • Persistence ignorance (every TObject can be persisted)
  • Support for One-One and One-Many relations (still no many-many)
  • Support for LazyLoading (you can enable§/disable lazyloading by file or by code by per-basis needs)
  • Support for IdentityMap
  • Support for custom “finder” (you can still use complex SQL if you want)
  • Complete support for CRUD
  • Transactions
  • Built in logging system to log *EVERY* sql or action performed by the framework
  • Opened to multiple data access strategies (interfaced based, not inheritance based) for use with different database (now I’ve developed the firebird one using DBX)
  • Caching for RTTI (the TSession object have a single TRttiContext holding ALL metadata)

Code is still under heavely development.

Those are 2 test-method to show the use of DORM:

  1. TPerson = class(TObject)
  2. property Phones: TdormCollection…. //implements IList
  3. end;
  4.  
  5. TPhone = classs(TObject)
  6. end;
  7.  
  8. //and now the unit tests
  9.  
  10. procedure TTestDORMHasMany.Setup;
  11. begin
  12.   Session := TSession.Create;
  13.   Session.Configure(TStreamReader.Create('dorm.conf'));
  14. end;
  15.  
  16. procedure TTestDORMHasMany.TearDown;
  17. begin
  18.   Session.Free;
  19. end;
  20.  
  21. procedure TTestDORMHasMany.TestHasManyLazyLoad;
  22. var
  23.   p: TPerson;
  24.   t: TPhone;
  25.   guid: string;
  26. begin
  27.   p := TPerson.NewPersona;  //static method. Return a fully populated TPerson object
  28.   try
  29.     t := TPhone.Create;
  30.     p.Phones.Add(t);
  31.     Session.Save(p);
  32.     guid := p.guid;  //GUIDs, or other PK types, are generated automagically by DORM. Obviously there is a specific class loaded to do this specified in the dorm.conf file)
  33.   finally
  34.     Session.Commit;
  35.   end;
  36.   Session.StartTransaction;
  37.  
  38.   // Test with lazy load ON
  39.   Session.SetLazyLoadFor(TypeInfo(TPerson), 'Phones', true);
  40.   p := Session.Load(TypeInfo(TPerson), guid) as TPerson;
  41.   try
  42.     CheckEquals(0, p.Phones.Count);
  43.   finally
  44.     Session.Commit;
  45.   end;
  46.  
  47.   Session.StartTransaction;
  48.   // Test with lazy load OFF
  49.   Session.SetLazyLoadFor(TypeInfo(TPerson), 'Phones', false);
  50.   p := Session.Load(TypeInfo(TPerson), guid) as TPerson; // Without commit, AV becouse IdentityMap doesn't work properly
  51.   try
  52.     CheckEquals(1, p.Phones.Count); // Child objects are loaded
  53.   finally
  54.     Session.Commit;
  55.   end;
  56. end;
  57.  
  58. procedure TTestDORMHasMany.TestLoadHasMany;
  59. var
  60.   list: IList;
  61.   t, t1: TPhone;
  62.   p: TPerson;
  63.   guid: string;
  64. begin
  65.   p := TPerson.NewPersona;  //static method. Return a fully populated TPerson object
  66.   try
  67.     t := TPhone.Create;
  68.     t.Numero := '555-7765123';
  69.     t.Kind := 'Casa';
  70.     p.Phones.Add(t);
  71.  
  72.     t1 := TPhone.Create;
  73.     t1.Number := '555-7765123';
  74.     t1.Kind := 'Casa';
  75.     p.Phones.Add(t1);
  76.     Session.Save(p); // save Person and Phones
  77.     guid := p.guid;
  78.   finally
  79.     Session.Commit;
  80.   end;
  81.  
  82.   Session.StartTransaction;
  83.   p := Session.Load(TypeInfo(TPerson), guid) as TPerson;
  84.   try
  85.     CheckEquals(2, p.Phones.Count);
  86.   finally
  87.     Session.Commit;
  88.   end;
  89. end;

Mapping, contained in a file called “dorm.conf”, is similar to the following:

  1. {
  2.   "persistence": {
  3.     "database_adapter": "dorm.adapter.Firebird.TFirebirdPersistStrategy",
  4.     "database_connection_string":"127.0.0.1:C:\\MyProjects\\DORM\\experiments\\dorm.fdb",
  5.     "username": "sysdba",
  6.     "password":"masterkey"
  7.     },
  8.   "config": {
  9.     "package": "dorm.bo.Person",
  10.     "logger_class_name": "dorm.loggers.FileLog.TdormFileLog"
  11.   },      
  12.   "mapping":
  13.     {      
  14.       "TPerson":
  15.       {
  16.         "table": "people",
  17.         "id": {"name":"guid", "field":"guid", "field_type":"string", "size": 100, "default_value": ""},
  18.         "fields":[
  19.           {"name":"firstname", "field":"first_name", "field_type":"string", "size": 100},
  20.           {"name":"lastname", "field":"last_name", "field_type":"string", "size": 100},
  21.           {"name":"age", "field":"age", "field_type":"integer"},
  22.           {"name":"borndate", "field":"born_date", "field_type":"date"}
  23.           ],
  24.         "has_many":[{
  25.           "name": "Phones",
  26.           "class_name":"TPhone",
  27.           "child_field_name":"guid_person",
  28.           "lazy_load": false
  29.         }],
  30.         "has_one": {
  31.           "name": "car",
  32.           "class_name":"TCar",
  33.           "child_field_name":"guid_person"
  34.         }
  35.       },
  36.       "TPhone":
  37.       {
  38.         "table": "phones",
  39.         "id": {"name":"guid", "field":"guid", "field_type":"string", "size": 100, "default_value": ""},
  40.         "fields":[
  41.           {"name":"number", "field":"number", "field_type":"string", "size": 100},
  42.           {"name":"kind", "field":"kind", "field_type":"string", "size": 100},
  43.           {"name":"guid_person", "field":"guid_person", "field_type":"string", "size": 100}
  44.           ]
  45.       }
  46.     }
  47. }

The PODO (Plain Old Delphi Objects) can be binded to the VCL controls with a set of MediatingView (Model-GUI-Mediator Pattern) with an Observer mechanism to mantain things in synch.

Any comments? Someone interested?

A Simple start with MVP in Delphi for Win32, Part 2

Delphi 2010, Delphi Dependency Injection, Design Patterns, MVC, MVP, Uncategorized 2 Comments »

Some month ago I wrote a simple article about an MVP variant called PassiveView.
That example was very simple. Now I’ll present a more “advanced” version of that example.

The main problem with first example was the following method:

  1. procedure TfrmCalculatorView.FormCreate(Sender: TObject);
  2. begin
  3.   //Link controls with related interface
  4.   IFirstOperand := TGUIEdit.Create(EditFirstOp);
  5.   ISecondOperand := TGUIEdit.Create(EditSecondOp);
  6.   ICalcResult := TGUIEdit.Create(EditResult);
  7.   IOperators := TGUISelectableList.Create(ComboOperators);
  8.   IError := TGUIEdit.Create(EditError);
  9.  
  10.   //link view and presenter
  11.   FPresenter := TCalculatorPresenter.Create(Self); //<<– THIS IS THE BAD LINE
  12. end;

The “BAD” line links the View with the Presenter but it’s in the view code, so this is meaning that View KNOWS the presenter… and this is not a good thing becouse the view is not so “passive”.

In a more advanced (and complex) version the View should be completely ignorant about the class that implement the presenter and the service.

In the main dpr file now the code now looks like the following.

  1. var
  2.   MainPresenter: ICalculatorPresenter;
  3.   CalculatorView: TForm;
  4. begin
  5.   Application.Initialize;
  6.   Application.MainFormOnTaskbar := True;
  7.   //SETUP THE MAIN APPLICATION FORM FOR VCL PURPOSE
  8.   Application.CreateForm(TfrmCalculatorView, CalculatorView);  
  9.   //SETUP ALL THE LINKS BETWEEN THE MVP TRIAD
  10.   MainPresenter := TCalculatorPresenter.Create(CalculatorView as ICalculatorView, TCalculatorService.Create);
  11.   //LETS START!
  12.   Application.Run;
  13. end.

Now the presenter take care about all links between the MVP triad.

  1. constructor TCalculatorPresenter.Create(CalculatorView: ICalculatorView;
  2.   CalculatorService: ICalculatorService);
  3. begin
  4.   inherited Create;
  5.   FCalculatorService := CalculatorService;
  6.   FCalculatorView := CalculatorView;
  7.   FCalculatorView.SetPresenter(self);
  8.   InitView;  //does the links
  9. end;

There is another addition to the previous example. Now there is only one constructor in the presenter, and using dependency injection take 2 interface for the view and the service.

  1.     constructor Create(CalculatorView: ICalculatorView; CalculatorService: ICalculatorService);

Another plus is the possibility to open the same form a number of times without change the code for create it.

This is the GUI for this simple application.

3 instance of the same view with different presenter and service

3 instance of the same view with different presenter and service

As bonus, unit tests and mock object arent changed.

As usual the source code is here.

A simple Dependency Injection Container for Delphi

Delphi 2010, Delphi Dependency Injection, Design Patterns 9 Comments »

As wikipedia says:

“Dependency injection (DI) in computer programming refers to the process of supplying an external dependency to a software component. It is a specific form of inversion of control where the concern being inverted is the process of obtaining the needed dependency. The term was first coined by Martin Fowler to describe the mechanism more clearly.

Many of us have already read this historical article from Martin Fowler about dependency injection pattern, but actually there isn’t a real framework for implement dependency injection in Delphi.

There are already the following implementation for DI in Delphi

  • Emballo (work with pre-D2010 too, but the implementation require changes in the service classes. I really hate it)
  • Delphi Spring Framework (very nice, but still not realeased)

So, I decided to write my own simple DI framework.

You can find the code at google code project here: http://code.google.com/p/delphidicontainer/

This is the first public version and come with sample, documentation and unit tests.

Folow some sample code.

  1. program Test01;
  2. {$APPTYPE CONSOLE}
  3.  
  4. uses
  5.   SysUtils,
  6.   DIContainer in '..\..\src\DIContainer.pas',
  7.   ServiceTestObjectsU in '..\..\UnitTest\ServiceTestObjectsU.pas';
  8.  
  9. var
  10.   DIContainer: TDIContainer;
  11.   s1: TService1;
  12.   s2: TService2;
  13.   s3: TService3;
  14.   s6: TService6;
  15.   s7: TService7;
  16. begin
  17.   try
  18.     DIContainer := TDIContainer.Create;
  19.     try
  20.       // AddComponent with TClass with and   InitType = Singleton
  21.       DIContainer.AddComponent(TService1, TDIContainerInitType.Singleton);
  22.       // AddComponent with QualifiedName and InitType = Singleton
  23.       DIContainer.AddComponent('ServiceTestObjectsU.TService2',
  24.         TDIContainerInitType.Singleton);
  25.       // AddComponent with QualifiedName and InitType = CreateNewInstance
  26.       DIContainer.AddComponent('ServiceTestObjectsU.TService3',
  27.         TDIContainerInitType.CreateNewInstance);
  28.  
  29.       // GetComponent with QualifiedName
  30.       s1 := DIContainer.GetComponent('ServiceTestObjectsU.TService1')
  31.         as TService1;
  32.       s1.Message := 'I''m the first message';
  33.       WriteLn(s1.Message);
  34.  
  35.       // GetComponent with TClass
  36.       s2 := DIContainer.GetComponent(TService2) as TService2;
  37.       s2.Message := 'I''m the second message';
  38.       WriteLn(s2.Message);
  39.  
  40.       // GetComponent with a dependent service (TService3 depends upon TService1 and TService2)
  41.       s3 := DIContainer.GetComponent('ServiceTestObjectsU.TService3')
  42.         as TService3;
  43.       WriteLn(s3.GetCompoundMessage);
  44.       // s3 is not created as Singleton, so after use it I must free it
  45.       s3.Free;
  46.  
  47.       // AddComponent with QualifiedClassName, a custom initializer, an alias.
  48.       // Component will be created as singleton (single instance managed by Container)
  49.  
  50.       DIContainer.AddComponent(DIContainerUtils.GetQualifiedClassName
  51.           (TService6),
  52.           function: TObject
  53.           begin
  54.             Result := TService6.Create(DIContainer.Get(TService1) as TService1,DIContainer.Get(TService1) as TService1);
  55.           end,
  56.           'srv6',
  57.         TDIContainerInitType.Singleton);
  58.  
  59.       s6 := DIContainer.Get('srv6') as TService6;
  60.       WriteLn(s6.ToString);
  61.       s6 := DIContainer.Get('srv6') as TService6;
  62.       WriteLn(s6.ToString);
  63.  
  64.       // AddComponent with QualifiedClassName, a custom initializer, an alias.
  65.       // Component will be created as singleton (single instance managed by Container)
  66.       DIContainer.AddComponent(DIContainerUtils.GetQualifiedClassName
  67.           (TService7),
  68.             function: TObject
  69.             begin
  70.               Result := TService7.Create(DIContainer.Get(TService1) as TService1,DIContainer.Get(TService1) as TService1);
  71.             end,
  72.             'srv7intf',
  73.           TDIContainerInitType.Singleton);
  74.  
  75.       s7 := DIContainer.Get('srv7intf') as TService7;
  76.       WriteLn(s7.ToString);
  77.     finally
  78.       DIContainer.Free;
  79.     end;
  80.   except
  81.     on E: Exception do
  82.       WriteLn(E.ClassName, E.Message);
  83.   end;
  84.   readln;
  85. end.

DelphiDIContainer also handle constructor injection with automatic dependency resolution for service.

Enjoy and stay tuned.

My Speeches at ITDevCon 2009 (Italy, Verona)

Delphi 2010, bit Time Software 1 Comment »

Tomorrow I travel to Verona to the ITDevCon the Italian Delphi Conference.
The conference will be at 11-12 november, and will be REALLY great!

You can see the time table here.

At this conference I will speak about:

  • Marshal e UnMarshal in Delphi 2010
  • Enterprise Data Access patterns in Delphi
  • Delphi e i messaging systems… ZeroMQ + AMQP e ActiveMQ + STOMP

My speeches will be in italian language only.

See you in Verona!

A Simple start with MVP in Delphi for Win32, Part 1

CodeGear, Delphi for Win32, Design Patterns, MVC, MVP, Programming, Uncategorized 10 Comments »

As GUI framework such as VCL become more and more powerful, it’s common practice to let the UI layer do more than it should. Without a clear separation of responsibilities, the UI layer can often become an integral part of application and businness logic, but… this kind of responsabilities belongs to other layers of the application.
A design pattern (and his numberless variants), is especially well suited to solving this problem.

In this article I want to build a simple application using MVP pattern. Actually, pattern used is not “clear” MVP but his variation called Passive View.

Using Fowler words:

A perennial problem with building rich client systems is the complication of testing them. Most rich client frameworks were not built with automated testing in mind. Controlling these frameworks programaticly is often very difficult.

A Passive View handles this by reducing the behavior of the UI components to the absolute minimum by using a controller that not just handles responses to user events, but also does all the updating of the view. This allows testing to be focused on the controller with little risk of problems in the view.

Passive View ensures no dependecies between Model and View.

Passive View has no dependencies between view and model (Unlike most MVC-style triad)

Passive View has no dependencies between view and model (Unlike most MVC-style triad)

In this sample, “model” is a simple layer for application logic. In real world, “service layer” should incapsulate “application service” and “domain model“.

Application looks like following:

The Calculator

The Calculator

Div operator with result

Div operator with result

Div operator with a EDivByZero Exception

Div operator with a EDivByZero Exception

Connect View and Presenter
The view (the Form in VCL application) must implement an interface.

This interface should provide all method to interact with GUI:

  1. ICalculatorView = interface
  2.   ['{471E3657-C6CE-49A3-BCB4-8FA6AF611DAD}']
  3.   function FirstOperand: String;
  4.   function SecondOperand: String;
  5.   procedure SetFirstOperand(Value :String);
  6.   procedure SetSecondOperand(Value :String);
  7.   function GetOperator: IGUISelectableList;
  8.   procedure SetCalcResult(const Value: String);
  9.   procedure SetCalcResultReadOnly(const Value: Boolean);
  10.   function Error: IGUIEdit;
  11. end;

For simple interacting with GUI widget (in our example are EditFirstOperand, EditSecondoperand and EditCalcResult) we use a simple methods like following

  1.   function FirstOperand: String;
  2.   function SecondOperand: String;
  3.   procedure SetFirstOperand(Value :String);
  4.   procedure SetSecondOperand(Value :String);

But, if we need more by our widget (like populating combo box or change font color in the EditError or set ReadOnly to true) we should use another interface for a family of component.
In this sample I wrote 3 general interface:

  1.   IGUIBaseInterface = interface
  2.     ['{F0B7F031-9302-415E-8545-1FE20A365840}']
  3.   end;
  4.  
  5.   IGUIEdit = interface(IGUIBaseInterface)
  6.     ['{FE2D56FB-0CFB-4B33-9B56-0A523B235D37}']
  7.     procedure SetText(const Value: String);
  8.     function GetText: String;
  9.     function GetAsInteger: Integer;
  10.     function GetAsFloat: Extended;
  11.     procedure SetReadOnly(const AValue: boolean);
  12.     procedure SetVisible(const Value: Boolean);
  13.     function GetTextAsInteger: Integer;
  14.     procedure SetTextAsinteger(const Value: Integer);
  15.     function GetTextAsFloat: Extended;
  16.   end;
  17.  
  18.   IGUISelectableList = interface(IGUIBaseInterface)
  19.     ['{EEFE5C52-94C3-464B-80F2-05E443B0F0F6}']
  20.     procedure SetText(const Value: String);
  21.     function GetText: String;
  22.     procedure SetValue(const Value: String);
  23.     function GetValue: String;
  24.     function GetSelected: ISSKeyValue;
  25.     procedure AddPair(AKey, AValue: String);
  26.     procedure Clear;
  27.   end;

For implementation details see attached sample code.

Finally in FormCreate of our form we can wire Presenter and View:

  1. TfrmCalculatorView = class(TForm, ICalculatorView)
  2.   //code
  3. end;
  4.   //interface section
  5. procedure TfrmCalculatorView.FormCreate(Sender: TObject);
  6. begin
  7.   //Link controls with related interface
  8.   IOperators := TGUISelectableList.Create(ComboOperators);
  9.   IError := TGUIEdit.Create(EditError);
  10.  
  11.   //link view and presenter
  12.   //In this version VIEW know PRESENTER
  13.   FPresenter := TCalculatorPresenter.Create(Self);
  14. end;

This is a very simple example, so not all looks like real world. In a real world application, for example, view should not known the presenter class. With dependency injection you can do that (Next article in this serie will talk about this).

Every event generated by View (our Form) must be redirected to Presenter.

  1. procedure TfrmCalculatorView.Button1Click(Sender: TObject);
  2. begin
  3.   FPresenter.DoCalc;
  4. end;

Another approach is to publish some events in view interface and let presenter to bind them via standard event handler or anonimous methods (but this is for another post).

In attached sample code there is a sample application and unit test for Service Layer and View.
Required Mock Library is included in the zip file.

Simple Passive View, Sample Code

In 2nd part I’ll talk about unit test and mock object in Passive View.

Interview at PHP Day 2008

Design Patterns, Programming, Video 1 Comment »

Go to following link to see my interview after presentation (last PHPDay in may 2008).

I spoke with Andrea Marzilli (from www.ictv.it) about design patterns.

Interview is (only) in italian.

http://www.ictv.it/file/vedi/690/teti-design-pattern-e-php/

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in