Archive for August, 2009

Designing an Object in hcOPF

Sunday, August 23rd, 2009

hcOPF uses TField like attributes to represent the attributes of an object.  These ThcAttribute descendants provide an object with the ability to Undo any changes made to it’s attributes and notify other Delphi objects when changes have been made.  They are the foundation of the framework, providing a generic, re-usable way of representing object attributes without using simple types and Reflection with it’s limitations and overhead.  In order to specify the attributes for an object and how they’re stored and used, the developer needs to provide the framework with information in some form.  This is Metadata, and although it could be represented in various ways, including XML, in hcOPF it’s currently code.

MetaData is used by the framework to construct an object with the appropriate attributes, based on how the object is stored in an RDBMS.  Every ThcObject descendant must override the Register class method and register it’s metadata with the framework.  A typical Register method would be as follows:

class procedure TPerson.Register;
var
  MetaData: ThcMetaData;
  aDef :ThcAttributeDef;
begin
  MetaData := ThcMetaData.Create;
  with MetaData do
  begin
    with TableDefs.AddTableDef('Person','P',[tpUpdateable,tpInsertable]) do
    begin
      with AttributeDefs do
      begin
        aDef := AddDef('Person_ID',ftInteger,'ID',ftInteger,
                       [apPrimaryKey,apServerGeneratedBeforeInsert]);
        OID := ThcOID.Create('Person',[aDef]);
        AddDef('FirstName',ftString,'FirstName',ftString,[apRequired],30);
        AddDef('LastName',ftString,'LastName',ftString,[apRequired],30);
      end; //with
    end;  //with
  end;  // with
  ObjectRegistry.RegisterObject(TPerson,MetaData);
end;

The lifetime of the MetaData is managed for you, but since the Register procedure is not a callback, you must create the MetaData object.  Metadata contains the definition of the tables where the object attributes are stored and their relationships.  So you begin by adding a Table Definition to the Metadata, specifying the RDBMS table and whether the framework is allowed to insert or update rows in that table.  Then you add Attribute Definitions which essentially specify the column and it’s type in the RDBMS and the corresponding attribute name and type in the OPF.  The OPF is capable of translating database types into internal Delphi types and OPF attributes to some degree.  More on that later…

The final line in every Register() routine should be a call to register the object with the framework.  Namely:

ObjectRegistry.RegisterObject(TPerson,MetaData);

Then somewhere in the project you need to call the Register class method for all objects.  I typically do this in the initialization section of the same unit in which the business object is defined.  In the finalization section you should also call the UnRegister method, although technically this is not necessary since the application termination will reclaim the memory.

initialization
  TPerson.Register;
finalization
  TPerson.UnRegister;

In hcOPF a ThcAbstractFactory descendant is the object that is responsible for persisting a ThcObject descendant. To get a factory instance you request it from a FactoryPool.  All applications must contain 3 core objects:  a FactoryPool, a SQLMapper, and a TransactMgr.  Normally you would drop these components onto your main datamodule and hook them up like this:

The FactoryPool knows about the different factory classes available for persistence.  In this case, IBX has been chosen as the DAL for a FireBird database.  Once the datamodule has been created and the database is connected, you can load and persist objects as you wish.

For a simple example on using the framework with standard Delphi controls see the following.  This application has a rather contrived database structure used to show lazy loading (even though it’s triggered immediately) with a child object.  This example also shows how to display content from two different objects on the same form, and persist them in a single transaction.  The database included was created with Firebird 2.0.  I have included a script as well so you can use Interbase if you prefer.  I also included the EXE in case you want to run the application without setting up the framework to compile the project.

Breaking Up is Hard To Do

Tuesday, August 11th, 2009

Recently, I broke up a very large unit I had been working on for some time.  The unit contained objects that referenced each other in their type declaration.  This was possible since the declarations were resolved by the compiler in the same Type section, and the compiler allowed for this by way of forward declarations.

I started working on this unit long before I really understood the ‘circular references problem’.  I had read complaints about circular references before including a QC request to get rid of them altogether.  My answer at the time was simple; put them all in the same unit and use forward declarations.  The problem with this approach is as follows:

a) The unit grows to the point where you need GExperts, CodeRush or some other tool that provides rapid navigation to the class declaration and implementation.  Otherwise you spend more time finding stuff than writing code.

b) It becomes impossible to re-use one class in a different project.  The dependancies grow over time, and become very difficult to unravel at a later date.  Relying on dead code elimination is not advisable and compile times can increase dramatically for a little project if you start including some mammoth units.

c) More units, means easier group development.  If you’re using a CheckOut - Modify - CheckIn process, it will dramatically increase your ability to make changes.  In a Modify - Commit - Merge model, it will make merging changes easier.

d) fixing the problem is just plain painful.  I had to change numerous type declarations to use TObject and then cast the object reference to the appropriate type in the implementation.  This meant lots of compile cycles to find all the places I missed, and correct the uses clauses in all the new units.  On top of this grief was the fact I references private fields of other objects, so when I broke them apart, I had to add properties on some objects and resolve all the ‘undeclared identifier’ compiler errors.  It reminded me how much I hated the loose scoping rules Delphi originally had.  The earlier I had done this, the less painful it would have been, but I never stuck it out to the end before (about 10 hours worth of work).

What I re/learned?

The value of a simple principle.  One Class Per Unit.  Think of it as the Single Responsibility Principle for Units.  If you start developing using this mantra, you can never go wrong.  It will force better class design, and reduce class coupling giving you more re-usability in the process.