Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

TierAdapter Framework: Creating an application from scratch - Part 1
Ruben Rovira, August 1, 2005
In this new issue, we will show the steps to follow to develop an application from zero. Also, as mentioned at the end of the previous issue, we will demonstrate that very little code has to be written in order for everything to work correctly.
Summary
In this new issue, we will show the steps to follow to develop an application from zero. Also, as mentioned at the end of the previous issue, we will demonstrate that very little code has to be written in order for everything to work correctly.
Description

In the two previous issues of the series of articles on TierAdapter (see the magazines from June 2005 and July 2005), we saw the Demo application in detail, showing each of the objects which the framework provides. In this new issue, we will show the steps to follow to develop an application from zero. Also, as mentioned at the end of the previous issue, we will demonstrate that very little code has to be written in order for everything to work correctly.

Remember that you can download the latest version of TierAdapter and the complete Demo application, including source code, from the main page of our framework at SourceForge.

Creating subclasses from classes of the framework

As usual, before using any third-party tool, including Visual FoxPro itself out of the box, it is recommended to create a set of subclasses of the native classes of the tools to be used. This will give us the ability to extend and/or modify the behavior of these classes according to our needs, and eventually, it will serve as an isolation layer for possible changes in interface and/or behavior of the original classes in a future version of the tools used. For example, currently the method ToXML of the class XMLAdapter has the following interface:

XMLAdapter.ToXML( cXMLDocument [, cSchemaLocation [, lFile [, lIncludeBefore [, lChangesOnly ]]]] )

Let's assume that in the next version of VFP, the VFP team decides to change the interface for this class and to eliminate the first parameter. Fortunately, the Team doesn't usually do this kind of stuff, but not all developers give the same guarantees. If, as users of the tool, we would have committed the mistake of directly using the native VFP XMLAdapter class, we would have to modify every part of the code where we use the method ToXML. On the other hand, being prudent, we would have created our own class MyXMLAdapter based on the native class, and used it in a similar way, but in the case of this change, we would only have to modify the method of our class to make it compatible with the new base class.

In the case of TierAdapter, on principle we should create subclass of the following objects:

  • Application (See msApp.prg)
  • Splash Screen, if we want to use any (see msSplashScreen.vcx)
  • AppLogo, if we want to use any (see AppLogo.prg)
  • The base controls of the framework based on the native VFP controls, with some additional behavior (msBaseClass.vcx and msBaseClass.prg)
  • The ToolBar (see msMainToolbar.vcx)
  • The form called ActionForm which has the ability of automatically synchronizing with the ToolBar and with the methods of the user layer of the associated entity (see msActionForm.vcx)

The same can be applied to the remainder of the classes provided.

The main program

To put our new development to work, we only need a small program that creates the application object (see Demo\Source\Main.prg). Upon being created, this application object (see msApp.prg), configures the application environment, shows the welcome screen, manages the user login, creates the main menu according to the user rights, creates the toolbar, etc., and places the application in a wait state through a READ EVENTS.

If you take a look at Demo\Source\Main.prg, you will note that a subclass of the application object called "DemoApp" (see also "Demo\Source\DemoApp.prg") was created, and that within it, there are references to the objects that define the welcome screen and the logo to be used on the desktop, which, as we saw before, are subclasses adapted to this particular Demo.

   cSplashClass         = "DemoSplashScreen"
   cSplashClassLibrary  = "source\DemoSplashScreen.vcx"

   cAppLogoClass        = "DemoLogo"
   cAppLogoClassLibrary = "Source\DemoLogo.Prg"

The system menu

The application's main menu consists of 4 submenus. The first one from the left is the menu called "ToolsMenu". Immediately to its right, the submenus for the specific application are generated. If we are executing the application in development mode, the next submenu that appears is called "DeveloperMenu", and finally, to its right, the submenu called "TrailerMenu" is added.

The menus "ToolsMenu", "DeveloperMenu" and "TrailerMenu" are provided by the framework, and are generated with the VFP Menu Builder. They can be freely modified, either to add functionality, to change the language, etc. In the application object there are three properties to indicate which menus to use; these properties are "cToolsMenu", "cDeveloperMenu" and "cTrailerMenu", defined by default as follows:

   cToolsMenu     = "taMenu.mpr"
   cDeveloperMenu = "taDevelopTools.mpr"
   cTrailerMenu   = "taTrailerMenu.mpr"

With respect to the specific menu for each particular system, it is generated dynamically according to the privileges of the user and the group or groups to which he belongs. The information required to generate the menu is stored in the system database (see taSystem.dbc). The table Access.dbf contains the general data, whereas the table UserAccess.dbf contains the accesses enabled for each user. A group is considered a user, and its data is stored in the same table User.dbf (we will see this in the following articles, when we talk about the framework's security module).

With this information, on application start, immediately after the user gives his credentials, the method oApp.oMenuBuilder.BuildMenu() is called (see uoUser.prg and taMenuVFP.prg) for creating the menu.

This information about the system, accesses, users and groups, is contained in a Visual FoxPro DBC, but it can be passed to the database engine of your choice.

Unfortunately, for lack of time, we were not yet able to develop some sort of builder to make menu creation easier. To create the menu, you have to directly edit the table Access.dbf or create your own tool. In this latter case, if you want to contribute with all users, you can send it to us, and it will be integrated into the framework.

The tiers

The framework presents an n-tier model, separating the data layer, the business layer and the user layer.

In the data tier we place everything which is realted to persistence, the table or tables that store the data, the mode of conection to the database, the definition of views, calls to stored procedures, etc.

The business tier contains everything related to business rules; it is its responsibility to enforce them, through required validations.

The user tier fulfills the role of providing services at the user interface level, and maintains information required for acting on objects such as ActionForms, PickOnes and Selectors of the entity represented.

The model briefly described is implemented in the framework through a class hierarchy, which we show in the following image:

Figure 1: Class Diagram

As we can see, at the layer of the greatest abstraction we find the TierAdapter class (see TierAdapter.prg), which defines common properties and methods, some implemented at this level and others abstracted so that they can be implemented at lower levels, according to the responsibilities of each tier.

We will now name the most important methods, and briefly indicate their function.

  • GetOne (Abstract): Obtains an existing individual from the related entity.
  • GetAll (Abstract): Obtains a set of individuals from the related entity.
  • New (Abstract): Creates a new individual of the related entity.
  • Put (Abstract): Saves data of a new individual, or changes to an existing one.
  • GetData (Implemented): Receives the XML with data from the previous tier, and transforms them to cursors.
  • SendData (Implemented): Converts the data to XML, and sends them to the following tier.
  • NextTier (Implemented): Factory method that creates the following tier.

The property colTables is of type collection, and stores information about the related table or tables related to the entity. Further on we will see this in greater detail.

Descending a level in the hierarchy, we find the classes UserTierAdapter, BusinessTierAdapter and DataTierAdapter, all of them subclasses of TierAdapter, which represent, as their names indicate, the user tier, the business tier and the data tier, respectively.

The user tier (see UserTierAdapter.prg) implements some abstract methods of the TierAdapter class, and adds some other abstract methods to be implemented further on in concrete classes, such as CreateIndexes. From the methods implemented at this level, we will take method Put as an example. We see that at this level, it is in charge of obtaining the differences made to the cursors that contain the entity represented in a DiffGram, which is sent to the business tier, where it is validated and then passed to the data tier, where it is finally sent to the corresponding tables.

The business tier (see BusinessTierAdapter.prg) doesn't implement methods, since, being in charge of reflecting the rules of each specific business, they should be implemented in succesive subclasses, having to do with concrete entities.

The data tier (see DataTierAdapter.prg) adds some methods of its own to its functionality and implements them, such as ConnectToBackend or DisconnectToBackend; it also implements abstract methods of the class TierAdapter. Following the example of the Put method, this tier is in charge of updating the tables with the modifications done in the cursors sent from the upper tiers, managing the connection to the database, transactions, etc.

The framework also provides the first level of implementation for every one of the classes mentioned. For UserTierAdapter, we have uoGeneral (see uoGeneral.prg), for BusinessTierAdapter we have boGeneral (see boGeneral.prg), and for DataTierAdapter we have doGeneral (see doGeneral.prg). These will be the classes from which we have to subclass in order to create a concrete entity, in every one of the tiers. We will see how this is done further on, when we create our first concrete entity, but first, we have a comment about the collection of tables in an entity.

The collection of tables in an entity

In the typical relational data model, the entities are represented by one or more tables, related among them. The TierAdapter classes have to be provided with certain information from these tables as well as their relations, in order to be able to act. This information is provided at the DataTierAdapter level, and is propagated automatically to the remaining tiers.

The data that has to be provided for each table is the following:

  1. Name of the table.
  2. Name of the cursor to be generated.
  3. In case of entities with 2 or more tables with parent/child relations (example, Orders and OrdersDetail), indicate the name of the parent table. Leave blank if the table has no parent.
  4. In the case of a table being a child table, indicate the field with which it relates to the parent table, as a Foreign Key.
  5. If the table is at a level of 3 or more with respect to the main table of the entity, that is, parent/child/grandchild/..., it is necessary to indicate the field with which the table relates to the table at level 1.
  6. It is possible to define a SELECT-SQL to be used in the NEW and GETONE methods.
  7. Name of the table's Primary Key.
  8. Indicate whether the PK is modifiable, or is an autoincremental field.

If we consider, as an example, the concrete entity "Product" from the Demo, and look at its data tier (doProduct.prg), we find, in its Init method, the following definition for the table:

   Define Class doProduct AS doGeneral of doGeneral.prg OLEPUBLIC
      ...
      Procedure Init
         DoDefault()
         This.ColTables.AddTable( 'Products', ;
                                  'cProduct', ;
                                  '', ;
                                  '', ;
                                  '', ;
                                  '', ;
                                  'ProductID', ;
                                  .F. )
         Return ( This.ColTables.Validate() )
      EndProc
      ...
   EndDefine 

Here we see how a single member is added to the collection of tables from the entity, declaring the Products is the underlying table, that its data shall be returend in a cursor called cProduct, that the table's Primary Key is the field ProductId, and that this field is not updatable since it is an Identity field in SQL Server, or an Autoincremental field in VFP.

The level of each cursor is determined automatically when validating the table collection. The level values are indicated as 1 (one) for the main level of the entity, and they are increased by 1 (one) for each level below it. It is a requirement of the framework that an entity have one and only one main table at the level 1, but then, you can have one or more child tables at level 2 (two), which can, in turn, have one or more child tables at level 3 (three), etc.

From the data tier, information is sent to the other tiers about the name of the cursors, their level in the hierarchy and their primary key. This information is stored in the table collection in each tier (see the property colTables, and colTables.prg).

The classes receive the information about the entity that they represent between tiers, through XML. Each tier has the intelligence required to make this data serialization transparent, and it frees us from the requried conversions, providing us VFP cursors on every level.

Creating a concrete entity

To represent a concrete entity, we have to create at least one class to represent it at the data tier level, one for the business tier, and one for the user tier. In every one of these classes it is necessary to modify the values of some properties. Explaining this in theory would be quite extensive, so, as an example, we are going to use the Customers entity from the Demo. Let's start with the data tier.

Entity "Customers" – Data tier

   Define Class doCustomer AS doGeneral of doGeneral.prg OLEPUBLIC

      cGetAllFields = "CompanyName, CustomerID"
      cGetAllPaginatedOrderBy = 'CompanyName, CustomerID'

      Procedure Init
         DoDefault()
         This.ColTables.AddTable( 'Customers', ;
                                  'cCustomer', ;
                                  '', ;
                                  '', ;
                                  '', ;
                                  '', ;
                                  'CustomerID', ;
                                  .T. )
         Return ( This.ColTables.Validate() )
      EndProc 

   EndDefine

In the code of the data tier we find two properties, cGetAllFields and cGetAllPaginatedOrderBy. In the first property we define the fields which method GetAll returns by default. In the second property, we define the sorting that is to be used to return pages of data.

The Init method is similar to the class Products we saw before, except that the PrimaryKey of this table is updatable, therefore, the last parameter is set to TRUE.

Entity "Customers" – Business tier

   Define Class boCustomer AS boGeneral of boGeneral.prg OLEPUBLIC
      cTierClass  = "doCustomer"
   EndDefine

In the specialization of the business tier for the entity Customers, we only find the property cTierClass, where we define in which class the definition of the data tier to be instantiated at this level is found. Given the absolute lack of business rules in our Demo, the definition of this class is not overly complex.

Entity "Customers" – User tier

   Define Class uoCustomer AS uoGeneral of uoGeneral.prg

      cTierClass  = "boCustomer"

      cSelectorFormName = "CustomersSelector"
      cSelectorFields = "CompanyName, CustomerID"
      cSelectorCaptions = "Company Name, Customer ID"

      cPickOneCursorFields = "CustomerID, CustomerID, CompanyName"
      cPickOneTableFields = "CustomerID, CustomerID, CompanyName"

      cType ='C'
      cFormat = '!'
      cInputMask = 'XXXXX'

      cFormName = "Customer"

   EndDefine 

Just as with the previous tier, we find the property cTierClass which allows us to indicate the class which contains the associated business tier.

The other properties include three whose name starts with cSelector...; they allow us to provide the framework useful information for the functioning of the Selector objects to select individuals from the entity. The property cSelectorFormName contains the name of the form where the Selector for the entity specializes. In case you leave this property blank, a generic Selector is used (see GenericSelector.scx). Properties cSelectorFields and cSelectorCaptions contain the names of the fields to include, and the title that will be shown in the Selector's grid header.

The remaining properties let us supply information that will be used with the object PickOne. The cursor and table fields that will be used are in cPickOneCursorFields and cPickOneTableFields, respectively. The type, format and mask of the code field are contained in the properties cType, cFormat and cInputMask. cFormName contains the name of the form where the data from the entity is shown; this form can be accessed through the PickOne context menu.

As we can see, the specifics of each entity can be described easily in the different tiers, and once we are finished defining it as we have seen, we only need to instantiate a form. Let's see how to do this.

Note: We recommend naming the classes created as shown in the example of entity Customers, that is, doEntity for the data tier, boEntity for the business tier, and uoEntity for the user tier. As any notation rule, respecting it will help us remember the different names of the classes that make up the entity in the different tiers.

The form "Customers"

The forms to input and query data from the entities should be based on a subclass of msActionForm (see msActionForm.vcx), since this class has the intelligence required to do tasks such as Select, Open, Save, etc., based on services provided by the framework.

Before continuing with other subjecdts, following with the entity Customers as an example, let's see how the finished form looks.

Figure 2: The form "Customers"

In the previous image we see that once the corresponding subclass is created, we only need to add the data input objects which we consider appropriate, and indicate, in the corresponding ControlSource property, the field to which it relates, and the cursor to which it belongs. For example, for the TextBox txtCompanyName, the ControlSource is cCustomer.CompanyName.

Once the required objects, related to the fields of the cursor cCustomer, are created, we only need to add a cmdFastEdit object. This is the CommandButton located to the right of the CustomerID in the previous image. It is defined in library msActionForm.vcx, and when the framework detects it in an msActionForm, it automatically allows us to edit the data from the record shown, without having to select an individual through a Selecdtor.

Without adding absolutely any code, we have to change the following form properties:

  • Caption: Put something creative, for example, "Customers".
  • Name: Make it "frmCustomer".
  • cUserTierClass: Here we should indicate the name of the class that contains the user tier for the entity (in our case, uoCustomer).
  • cReportAll: Here you should indicate the name of a report that will be executed when the form is in the wait state.
  • cReportOne: Here you should indicate the name of a report that is executed when the form is in edit state.

Let us now see the property window (in the example, no reports have been specified):

Figure 3: Property window of form "Customers"

Creating the classes for every one of the tiers, the form as we have seen, and adding a menu item, is enough to have an ABM, such as for the entity Customer.

Conclusion

As we have seen, it is very simple to define the attributes of the entities, for the correct working of the framework. Once we have finished this task, we only need to write some code to apply the business rules, and we have a working application.

In the next issue, we will continue the development of an application from zero, step by step. We shall see – among other things – how to develop a specific Selector object for an entity, the specialization of classes for an entity with related table in Header/Detail mode, and error handling in the framework. See you next month.

Ruben Rovira, Desarrollos Independientes
Rubén O. Rovira, (Buenos Aires, Argentina), is a Computer Bachelor. He is devoted to development of custom software for commercial and service companies since 1992. He has used FoxPro and Visual FoxPro as a development tool since version 2.5 (DOS). Nowadays he is an independent Information Systems Consultant, and Lead Developer of the TierAdapter Framework.

Omar Bellio, Soluciones Informáticas
Omar Bellio is a system analyst and independent developer since 1985. He has worked with Fox since its very first version. Today he develops applications for a variety of purposes using Microsoft tools (VFP, VB, .NET PLATFORM, SQL Server, etc.) and also Oracle.
More articles from this author
Ruben Rovira, May 1, 2003
In this article I will talk about ASP.Net mobile controls, and how to use them to puch the functionality of our applications to the domain of mobile devices such as cell phones and PDAs. I will also create a simple web mobile application as an example, using a component developed in VFP8 to sh...
Ruben Rovira, September 1, 2002
In this article we shall see how to use images in forms and reports, in our applications developed with Visual FoxPro. We will also talk about how to optimally organize and save images on disk. Introduction How often have we, as developers in the DOS era, desired to have the possibility t...
Ruben Rovira, June 1, 2005
TierAdapter is an n-tier application development framework developed in Visual FoxPro. Briefly, it implements a hierarchy of classes that makes it possible to quickly develop components for the data access tier, easy to change between the native VFP engine and SQL Server or other engines available t...
Ruben Rovira, July 1, 2005
In this second issue of the series about the framework, we review the Demo application, the way it works and the main features it provides, so in the future issues we can explain how to implement a solution.
Ruben Rovira, September 1, 2005
In this issue, we will continue with the creation of the layers for an entity that includes more than one table, in a header-detail schema; as we shall see, also in this case, the required code is minimal.
Ruben Rovira, December 1, 2005
This time, we will briefly make a detour from the transactions (until the next issue), and look at different ways in which the framework allows us to distribute application components. This will serve as a basis, so that we can later analyze how to use transactions in every one of these cases.
Ruben Rovira, October 1, 2005
The basic idea is to place, within a Try/Catch block, a call to any process that might generate an exception (is there any process that can't?). When the exception is generated, it will be trapped by the Catch command, and processed differently, depending on the tier where it happens. The tier which...
Ruben Rovira, November 1, 2005
Any database engine, in order to be worthy of receiving this designation, should offer the possibility of handling transactions. The appropriate handling of transactions (among other things) allows us to guarantee the integrity of the stored data. For those with less experience in this subject, I sh...
Ruben Rovira, April 1, 2003
In the following paragraphs I shall try to summarize what web services are and later, step by step, I'll explain how to create a web service usign VFP8. This web service will be used in the third part of this note where, always from VFP8, we'll develop a small application that will consume the ...