t e m p o r a l 
 d o o r w a y 

Designing for Legacy System Support

 

When transitioning to a new development system, it is often necessary to support a legacy system - databases, processes, routines, etc.

The problem is that many programmers scatter legacy support throughout their system, and this makes obsoleting legacy support a very error prone process. Yet object-orientation, and, especially C++ Builder's component architecture, can make it possible to avoid all of these problems.

For the sake of this article, let's consider a simple system. In this system, there is a legacy database consisting of an old Account table and a new Account table, which has additional fields. The old Account table needs to be maintained to support some legacy programming which uses it.

First, we define a new class and base it on TDBDataSet. Thus, it can be dropped into a DataModule, it exports the functions and properties of a data set, and once the legacy facilities are obsolete, it can be simply be slightly altered (mostly by removing code) to be no more than the current database account table. Such a change can occur without requiring any external code changes.

This approach requires overriding many or all of the functions and properties of the TDBDataset, not all of which are shown in this example.

class Account: public TDBDataSet
{
   public:

      __fastcall Account(void);
      __fastcall ~Account(void);

      bool __fastcall FindFirst(void);
      bool __fastcall FindNext(void);
      // ... any other TDataSet methods to be overriden

      // Field interfaces
      __property AnsiString ID = {read=GetID,write=SetID};
      __property int OrderCount = {read=GetOrderCount,write=SetOrderCount};
      __property int AccountType = {read=GetAccountType,write=SetAccountType};
      // ... etc.

   private:

      // Get and set function interfaces... //

      TQuery *myQuery;
      TField *myCurrentID;
      TField *myLegacyID;
      TField *myCurrentOrderCount;
      TField *myLegacyOrderCount;
      TField *myCurrentAccountType;
}

The implementation is where the rubber meets the road. We will assume that the ID fields are compatible, and that none of the fields in the two databases have the same name. We will assume that both databases are accessible through the BDE (Borland Database Engine), either with ODBC or native drivers. Thus, we can use the BDE's support for joining across databases (heterogenous joins) to make our job easier.

__fastcall Account::Account(void)
{
   myQuery = new TQuery;
   myQuery->RequestLive = TRUE;

   // Observe the heterogenous join notation for the TQuery
   // :dbname:tablename gives the name of the database followed by the name of the table
   // SQL requires surrounding quotes because of the ":", and those must be escaped with "\"
   // The name after the database/table name is a name that can be used in the WHERE to distinguish fields from each database

   myQuery->SQL->Add("SELECT * FROM \":Current:Account\" Current,\":Legacy:Account\" Legacy WHERE Current.ID = Legacy.Acctnum");
   myQuery->Open();

   // Note the caching of the TFields, which improves performance

   myCurrentID = myQuery->FieldByName("ID");
   myLegacyID = myQuery->FieldByName("Acctnum");
   myCurrentOrderCount = myQuery->FieldByName("OrderCount");
   myLegacyOrderCount = myQuery->FieldByName("ORDCOUNT");
   myCurrentAccountType = myQuery->FieldByName("AccountType");
}
...
bool __fastcall Account::FindFirst(void)
{
   return myQuery->FindFirst();
}
...
AnsiString __fastcall Account::GetOrderCount(void)
{
   return myCurrentOrderCount->AsString;
}
...
void __fastcall Account::SetOrderCount(int theOrderCount)
{
   CurrentOrderCount->AsInteger = theOrderCount;
   LegacyOrderCount->AsInteger = theOrderCount;
}
...

Here you can see how the Account object encapsulates the updates and control over the legacy and current databases in a single object, hiding the presence of the twin tables, and allowing you the freedom to later eliminate the legacy support with no consequences for the rest of your system.

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved