3 Tiers, 2 Models, and XML Streams

Tony Marston - 23rd September 2000
Amended - 14th August 2002

This document replaces an earlier version called 'UNIFACE and the N-Tier Architecture'

1. The 2-Tier Structure ('Fat' client)
2. The 3-Tier Structure ('Thin' client)
3. What type of Service Component?
3a. Object Services
3b. XML Streams
3c. The differences between Object Services and XML Streams
3d. How an XML Stream works
3e. Miscellaneous Observations
4. Two Application Models - the BAM and the PAM
4a. The XAMPLE database (BAM)
4b. The USER database (PAM)
4c. Ideas that I have rejected
4d. Components in the Presentation layer
4e. Document Type Definitions
5. Operations in the XML (Session) Service Component
5a. GetData
5b. PutData
5c. Validate (client/server only)
5d. ValidateKey (client/server only)
5e. Verify_Delete
5f. UCOMMIT and UROLLBACK
6. CONCLUSION

1. The 2-Tier Structure ('Fat' client)

The most common method of developing a client/server system in UNIFACE is to use the traditional 2-Tier structure. This consists of a primary tier (or layer) which incorporates all presentation and business logic, and a secondary tier (or layer) which contains all data access logic. This can be represented in the following diagram:

Figure 1 - The 2-Tier Structure

3tiers2modelsXMLstreams1.gif

For those of you who are unfamiliar with this terminology, which appears to change whenever a new methodology becomes fashionable, here is a translation table:

The Presentation/Business Layer is provided by UNIFACE form components, perhaps coupled with report and service components, which contain the code that was developed for the particular application.

The Data Layer is provided by the various database drivers which allow the UNIFACE application to communicate with any of the most common DBMS systems which are available today. The physical database can be switched from one vendor's product to another simply by making a change in the assignment file, and not by changing any code within the application.

The disadvantage of this structure is that several components may deal with the same business entity (or object) but differ only in the way that the data is presented. This therefore implies that these components contain their own copies of the same business logic, which results in the need to change the code in each and every copy whenever the business rules change.

The components in the primary layer of this type of structure are heavy and bloated with code, and have thus been given the name 'Fat Client'.


2. The 3-Tier Structure ('Thin' client)

This splits each of the three logical areas into its own layer. For this structure to work effectively there should be clearly defined interfaces between each of the layers. This should then enable components in one layer to be modified without requiring any changes to components in other layers. One example is changing the file system from one DBMS to another, or changing the user interface from one system to another (e.g. from client/server to the web).

The main advantage of this structure over the 2-Tier system is that all business logic can be defined once within the business layer and then shared by any number of components within the presentation layer. Any changes to business rules can therefore be made in one place and be instantly available throughout the whole application. This structure can be implemented in UNIFACE as follows:

Figure 2 - The 3-Tier Structure

3tiers2modelsXMLstreams2.gif

The components in the primary layer of this type of structure have much less code, and have thus been given the name 'Thin Client'.


3. What type of Service Component?

With version 7.2.04 of UNIFACE Compuware sought to aid the development of a 3-tier structure with the introduction of a new type of service component called an Object Service. A superior solution (in my opinion) has been made available in version 7.2.06 with the introduction of XML streams. First let us examine their characteristics:

3a. Object Services (known as 'entity services' in UNIFACE 8)
  1. An Object Service is linked to a single entity within an application model, or perhaps a pair of entities which have been defined in a one-to-many relationship.
  2. An Object Service is constructed from a component template supplied by Compuware - all that the developer has to identify is the name of the object entity (or pair of object entities).
  3. An entity may have only one Object Service.
  4. An Object Service is 'turned on' by going to the entity definition within the application model and changing the access path from 'DBMS Path' to 'Object Service', and by filling in the name of the particular service component. The entity then becomes what is known as an 'Object Entity'.
  5. After being re compiled with this information whenever any component accesses an Object Entity through its read, write and delete triggers all traffic is automatically routed through the Object Service.
  6. When converting an existing 2-tier system to use Object Services no changes need to be made in any component code - just re compile once the Object Services have been created and defined in the application model.

This can be represented in the following diagram:

Figure 3 - Object Services in action

3tiers2modelsXMLstreams3.gif

Note that with the exception of one-to-many pairs each entity will require its own object service.

3b. XML Streams and Session Services
  1. An XML Stream is constructed and deconstructed based on the specifications within a Document Type Definition (DTD) which is held in an application model and defined using the DTD editor.
  2. A DTD may contain one or more entities.
  3. Multiple entities may be arranged either as siblings or in a parent-child hierarchy.
  4. Where a DTD contains a particular arrangement of entities the component that references the XML stream based on that DTD must have the same arrangement of entities.
  5. An entity may appear in more than one DTD.
  6. A form will (usually) deal with a single DTD.
  7. A DTD may be referenced by more than one form.
  8. A DTD will usually require its own dedicated service component (known as a 'session service' in UNIFACE 8).
  9. The names of entities and fields within the DTD need not be the same as the names within the UNIFACE component - renaming can be achieved by means of element mapping.
  10. Compuware do not provide any component templates for dealing with XML streams - you will have to create your own, both for forms and services. Alternatively you can borrow the ones I created in my sample code.
  11. There is no quick and easy conversion path for existing components - all read, write, delete and lock triggers must be disabled in all form components, and all retrieve and store operations must be replaced with xmlload and xmlsave using an XML stream which is handled by a service component that deals with that DTD. From my own experience conversion can be made considerably easier by using component templates (refer to my article 'The Power of Component Templates' for more details).

This can be represented in the following diagram:

Figure 4 - Using XML Streams

3tiers2modelsXMLstreams4.gif

Note that the entire contents of a form can be handled with a single XML stream and therefore a single XML Session Service. It is possible to use different streams for such things as foreign entities, and each different stream will require its own service component.

3c. The differences between Object/Entity Services and XML Streams
  1. An Object/Entity Service is linked to an entity whereas an XML Stream is linked with a Document Type Definition (DTD) which may contain any number of entities in any structure.
  2. With Object/Entity Services the form component and the service component must reference the same entity definition, but with XML Streams they can be completely different (the only linking factor is the DTD).
  3. A form containing several entities may require a separate Object/Entity Service for each entity but if the structure is covered by a single DTD only a single XML Session Service is required.
  4. A form communicates with an Object/Entity Service via the standard read, write, delete and lock triggers, whereas the entire contents of an XML stream is saved or loaded with a single command and passed between components with operations on a Session Service.
3d. How an XML Stream works
  1. The form component asks the service component (Session Service) to supply it with data.
  2. The Session Service performs a retrieve to populate its structure with all the required entities and occurrences, followed by an xmlsave to construct the XML stream, which is then passed back to the form component.
  3. The form then populates its own structure from the XML stream with a single xmlload statement.
  4. The user makes whatever changes are required to the data on the form.
  5. When the user wishes to apply these changes to the database he does not perform a store - instead the form does an xmlsave to construct a new XML stream, then passes that stream to the Session Service.
  6. The Session Service performs an xmlload to populate its structure using the latest data, followed by a retrieve/reconnect to re-attach each occurrence to the database. This automatically identifies existing occurrences which are to be updated or deleted, or new occurrences which are to be added. Provided that all validation is performed successfully this is followed by a store and commit.

Because the form component does not obtain its data directly from, nor write its changes directly to the physical database, it deals with what are known as disconnected record sets. It obtains its data from an XML stream, and writes its changes to an XML stream. It is the responsibility of the Session Service to read from and write to the physical database (either directly or via Object/Entity Services).

The use of XML streams therefore moves all data access and business logic from the form component (presentation layer) to the session service component (business layer). It also means that components in the presentation layer can be more easily replaced with components written in a language other than UNIFACE. Any language that can deal with XML streams can be used. This is far easier than trying to find one that can deal with the ENTITY or OCCURRENCE parameter, or lists with the <GOLD>semi-colon separator.

Note that there is no allowance for stepped hitlists with the xmlsave statement - it will retrieve ALL available occurrences into a single XML stream. It is possible to prevent a full table scan on large tables by restricting the number of occurrences with the maxhits option in the read statement.

The method by which a presentation layer component obtains data via an XML stream can be illustrated in a process flow diagram as shown in figure 5. Note that this involves a change to the contents of the <read> trigger.

Figure 5 - obtaining data via an XML stream

3tiers2modelsXMLstreams5.gif

The method by which a presentation layer component stores data via an XML stream can be illustrated in a process flow diagram as shown in figure 6. Note that this involves a change to the contents of the <store> trigger.

Figure 6 - storing data via an XML stream

3tiers2modelsXMLstreams6.gif

These diagrams are explained in more detail in my 3 Tier Development Guidelines which were produced following the conversion of my entire sample application and Menu/Security system from 2-tier to 3-tier.

3e. Miscellaneous Observations

During my investigations I came across the following 'features' which were not covered in the documentation:

  1. Although the xmlsave command will cause the <READ> trigger to be fired on all necessary occurrences it will not fetch them into the structure and the XML stream will contain null values. To force the data to be fetched you can either paint one of the fields, or reference one in proc code after the read statement.
  2. If the DTD specifies a field name which exists on an entity which is not actually named in the DTD it is not necessary to create an entry in the mapping list for that field provided that the field name is unique within the component. For example, the DTD for Z_PERSON contains PERS_TYPE_DESC which is obtained from X_PERS_TYPE, but this is not specified in any mapping parameters.

4. Two Application Models - the BAM and the PAM

By this I mean having one model for the business/data layers (Business Application Model or BAM) and a separate model for the presentation layer (Presentation Application Model or PAM).

'Why should you want two different models?' I hear you ask. The reason is simple - the way that the data is held in the physical database (BAM) may be rather different from how it needs to be presented to the user. Data may have to be obtained from several entities which are joined in complex relationships, or constructed at run time using the contents of other fields. The presentation model (PAM) can therefore be a simplified version of the business model (BAM), and can hide the complexities of the physical database.

This arrangement also allows for the possibility that the physical database could be altered without requiring corresponding changes to large numbers of components - such changes could be limited to components in the business layer and not the presentation layer.

Readers may be aware that in May of this year I wrote an article entitled 'A separate Application Model for Presentation Layer'. This documented an attempt to provide this facility by making alterations to object services. It worked, but was far from perfect. I have now rebuilt that entire set of sample code using XML streams, and I have to say that the result is far more satisfactory and more suitable for use in a production environment.

4a. The XAMPLE database (BAM)

This is the application model which is referenced by all components in the Business and Data layers. It has the structure shown in figure 7:

Figure 7 - The XAMPLE database

3tiers2modelsXMLstreams7.gif

Each PERSON may have one or more addresses (PERS_ADDR) over a period of time, each with its own start and end dates. Each address has its own set of address lines (PERS_ADDR_LN), with blank lines not being stored in the database.

There are numerous OPTIONS available, but each one can be turned on or off for each PERSON. Options which are turned on for a person have records written to PERS_OPT_XREF.

4b. The USER database (PAM)

This is the application model which is referenced by all components in the Presentation Layer. It has the structure shown in figure 8:

Figure 8 - The USER database

3tiers2modelsXMLstreams8.gif

At first glance the XAMPLE and USER models look similar, but there are differences:

  1. In the USER model all read, write, delete and lock triggers have been disabled.
  2. The Z_PERSON entity contains additional fields PERS_TYPE_DESC (from foreign entity Z_PERS_TYPE), NODE_DESC (from foreign entity Z_TREE_NODE), PERSON_NAME (constructed from FIRST_NAME and LAST_NAME), and ADDRESS_TEXT (a concatenation of all address lines).
  3. The Z_PERS_ADDR entity contains a separate field for each address line, thus removing the need for the Z_PERS_ADDR_LN entity. It also contains PERSON_NAME.
  4. The Z_PERS_OPT_XREF entity contains additional fields OPTION_DESC and ACTION_FLAG.
  5. It is possible for fields which are mandatory in the BAM to be made optional in the PAM. Any missing data will be detected when the Session Service is called upon to validate or update. In my sample USER model only primary and candidate keys are marked as mandatory.
  6. Each entity in the PAM is served by a single DTD, and for convenience I gave each DTD the same name as the associated entity. Entities in the BAM do not have DTDs.
  7. As each entity in the PAM has a single DTD, and each DTD is handled by a single session service, I used the 'object service' field in the entity properties to hold the identity of the associated session service. This enabled me to create procedures that required only the entity name as a parameter as the DTD name and session service name can now be deduced with generic code.

You may notice that I have changed the prefix on each entity name between the two models. This was done just to ensure that the DTD mapping feature actually worked (it's not that I don't trust Compuware's code, but ………). In a production environment it is quite likely I would not change the entity names between the two models.

The ability to add fields to an entity which are constructed at run time by the service component is an extremely useful one. For example, descriptions from foreign entities can be added, such as PERS_TYPE_DESC and NODE_DESC to Z_PERSON, thus removing the need to paint those foreign entities in the form component when it is required to display those descriptions. Other fields may require more complicated code for their construction, such as ADDRESS_TEXT, but this is all transparent to the form component.

4c. Ideas that I have rejected:

Before I go any further let me identify some silly ideas proposed by some former colleagues:

Firstly, it has been said that because the elements that are referenced by the presentation layer do not come directly from the physical database that it is not necessary to define them in any application model - they can simply be defined as dummy entities and dummy fields in individual components as and when required. This attitude shows a total lack of understanding about the use of a repository-based language such as UNIFACE. The whole idea of a repository is that objects can be defined once and used many times over. Anyone who says that when you want to use an object you must type out its details in longhand instead of merely picking it out of a central repository does not have a place in today's world of software engineering and should take up a profession that is more suited to their talents, such as pig farming or road sweeping.

Secondly, having accepted that the elements referenced by the presentation layer should be defined in an application model, it has then been said that because they do not reside in the physical database that all entities and fields should be marked as 'non-database' and that no keys or relationships need be defined. I have worked briefly in such an environment, and as far as I am concerned such ideas are complete rubbish. In my USER model all entities and fields are defined as 'in database' for the following reasons:

  1. I need to define primary keys so that I can use the $keyfields function and putlistitems/id command to extract the primary key values from an occurrence for passing as a parameter to a child component. This cannot work if the key is not defined, nor will it work successfully if the key fields are defined as 'non-database'.
  2. I need to define relationships so that key values can be transported automatically between the two entities in a relationship. The alternative would be to insert proc code to perform this manually, but that is not efficient.
  3. By defining all fields in the presentation model as 'in database' I can make use of the $selectlist function so that each form component can supply the session service with a list of those fields which it actually requires. This means that any constructed fields that are not required do not have to be constructed, thus do not waste any processing effort.

As you can see my reasons for defining the entities and fields in the presentation model as 'in database' are entirely practical, not ideological. I deal in methods that work, not theories that don't.

4d. Components in the Presentation layer

Now let me identify the form components in my sample code that are used to present this data to the user:

This lists occurrences of PERSON, and has buttons which pass control to other forms.

U_LIST1.gif


This adds a new occurrence of PERSON to the database. Values for PERSON TYPE are picked from U_POP1. Values for NODE are picked from U_TREE_POP1 and U_POP1_4. Details for the first address are entered via screen U_AUX2.

U_ADD1.gif


This lists all available occurrences of PERSON TYPE and allows the user to pick one.

U_POP1.gif


This lists the contents of a structure hierarchy and allows the user to pick one. The tree type is chosen using form U_POP1_4.

U_TREE_POP1.gif


This shows all available occurrences of TREE TYPE and allows the user to pick one.

U_POP1_4.gif


This allows the details for a PERSON's first ADDRESS to be entered.

U_AUX2.gif


This shows the existing details for a PERSON and allows the user to change them. Values for PERSON TYPE are picked from U_POP1. Values for NODE are picked from U_TREE_POP1 and U_POP1_4. The address can be changed via screen U_MOD1_2.

U_MOD1.gif


This shows the existing details for a PERSON's address and allows the user to change them.

U_MOD1_2.gif


This shows the existing details for a PERSON, but does not allow changes to be made. The full address details can be examined using screen U_AUX1.

U_ENQ1.gif


This will display the full details of a PERSON's ADDRESS.

U_AUX1.gif


For the selected PERSON this will list all occurrences of OPTION and show whether each OPTION has been switched ON or OFF by means of a checkbox. The value of each checkbox can be modified directly, or the user can double-click anywhere on the line.

U_MULTI4A.gif


This will allow occurrences of PERSON_TYPE to be added, modified or deleted.

U_MULTI1.gif


4e. Document Type Definitions
DTD NameComments
Z_PERSON Contains entity Z_PERSON.
Used in form U_ADD1, U_DEL1, U_ENQ1, U_LIST1, U_MOD1
Z_PERS_ADDR Contains entity Z_PERS_ADDR.
Used in form U_ADD1, U_AUX1, U_AUX2, U_MOD1_2
Z_PERS_OPT_XREF Contains entity Z_PERS_OPT_XREF and Z_PERSON.
Used in form U_MULTI4A
Z_PERS_TYPE Contains entity Z_PERS_TYPE.
Used in form U_POP1, U_ADD1, U_MOD1
Z_TREE_NODE Contains entity Z_TREE_NODE.
Used in form U_TREE_POP1, U_ADD1, U_MOD1
Z_TREE_TYPE Contains entity Z_TREE_TYPE.
Used in form U_POP1_4, U_TREE_POP1

As you can see from this list it is possible to use the same DTD in more than one component. For example, Z_PERSON is used in form U_LIST1 to select and retrieve multiple occurrences, whereas the others deal with adding, deleting, displaying or modifying a single occurrence at a time.

It is also possible to use more than one DTD in the same form. For example, form U_ADD1 uses Z_PERSON to create the person details, and Z_PERS_ADDR to create the first address. Forms U_ADD1 and U_MOD1 also make use of the DTD's for Z_PERS_TYPE and Z_TREE_NODE to populate the foreign entities with new selections.

Z_PERS_OPT_XREF is constructed from two entities. It takes all the occurrences of X_OPTION then sets a checkbox on if a corresponding occurrence exists on X_PERS_OPT_XREF. When the xmlstream is returned for updating, occurrences of X_PERS_OPT_XREF are either created or deleted, depending on the value of this checkbox.

Because my form components and DTDs both reference entities from the USER model (PAM) no mapping parameters are required for manipulating XML streams in these components. Mapping is required in each of my session service components as they reference entities only from the XAMPLE model (BAM). Default mapping is defined using the DTD editor that is built into the UNIFACE IDE, therefore there is no need to specify any manual mapping.


5. Operations in the XML (Session) Service Components

Creating a component template for the Session Services was a relatively simple procedure. I eventually ended up with the following operations:

5a. GetData

GETDATA(profile, selectlist, orderby, options, xmlstream)

This is used instead of the read operation in the <read> trigger. By replacing the code in the <read> trigger I am still able to use the retrieve command.

5b. PutData

PUTDATA(xmlstream)

This is used instead of a store.

5c. Validate (client/server only)

VALIDATE(xmlstream, fieldname, changes)

This can be activated from the <validate occ> or <validate field> triggers in form components to give the same frequency of validation as is found in a 2-tier system. The stream is returned to the form in case any values are changed during the validation process, or if any error messages have been inserted into the valerr attribute of any field or occurrence by means of the $fieldproperties or $occproperties functions.

5d. ValidateKey (client/server only)

VALIDATEKEY(xmlstream, keynumber)

This can be activated from the <validate key> trigger in form components to give the same frequency of validation as is found in a 2-tier system.

5e. Verify_Delete

VERIFY_DELETE(xmlstream)

5f. UCOMMIT and UROLLBACK

UCOMMIT() or UROLLBACK()


6. CONCLUSION

My experiments have proved (to me at least) that the introduction of XML streams into UNIFACE provides a great step forward in the support of a 3-tier architecture. It is certainly more flexible than what had been offered earlier with object services. The ability to allow the presentation layer to use its own application model, a simplified version of the one used by the business layer, is an added feature that some may not know how to exploit. This article is my attempt to demonstrate how XML streams can be used in a 3-tier architecture and what benefits they can provide.

The software used in my experiments is available for download here from my web site so that you can check the results for yourself. Please feel free to play with it. Any comments would be most welcome.

Be aware that my sample code will not run perfectly unless you are running 7.2.06 with Service Pack 1 and Patch W640. There are some minor problems outstanding (see the following table), but these are not serious.

The problems I have reported to Compuware are as follows:

Log/SPR/BUG#DescriptionFixed?
Bug 23469 Never-ending loop after xmlload and retrieve/reconnect when using putlistitems/occ Patch W640
Bug 23469 Never-ending loop after retrieve/reconnect Patch W640
Log 27348 Retrieve/reconnect loses data on an 'up' entity Patch W640
Bug 23470 Change to foreign key value is lost with xmlload Patch W640
Bug 23433 VALIDATE command does not return -1 if associated validation returns -1 Service Pack 1
Bug 22735 Unable to map fields correctly in DTD Service Pack 1
Bug 23469 Non-database fields being corrupted after xmlload and retrieve/reconnect. Patch W640
Log 29917 XMLSAVE will abort in YRTLC.DLL if form contains fewer fields than are defined in the DTD (workaround is to set fieldlist to 'ALL'). Patch W672
Log 29938 RETRIEVE/RECONNECT is not detecting a duplicate key if $occstatus='new' and a record with the same primary key already exists (and a field is modified by the contents of the <format>/<deformat> trigger).  
Log 29942 The <QUIT> trigger in a form component will not fire if that form has child components which are services (but not object services). Fixed by removing code from the <QUIT> trigger in the service.
BUG 24107 $fieldproperties will not work if attempting remote validation in a self-contained service.  
SPR 50203 Field validation trigger fired because of <store> causes error in YRTLC.DLL when it reaches the DONE statement. Cannot use DISCARD in this trigger - see Call Log 31158
Log 30173 XMLSAVE is not picking up value for 'errormsg' set by $occproperties or $fieldproperties when performing remote validation.  
Log 30842 Performing a DISCARD after an XMLLOAD in the <validate field> trigger causes a page fault. Cannot use DISCARD in this trigger - see Call Log 31158
Log 30875 XMLLOAD will sometimes change $occstatus from 'est' to '', thus causing an existing occurrence to be treated as a new one and rejecting the primary key as a duplicate.  
Log 30949 XMLLOAD will create corrupt occurrences if the DTD contains a field with the same name as the entity.  
Log 31157
SPR 50394
An '/init' switch is needed on XMLLOAD so that no field or occurrence modification flags are set after data is loaded into the component.  
Log 31158
SPR 50395
A '/replace' switch is needed on XMLLOAD so that existing occurrences in the component are replaced rather than new occurrences appended.  
Log 31198 Remote validation not supported in field or occurrence triggers of client/server forms. Linked to Call Log 31157 and 31158
SPR 50504
BUG 24394
Occurrence which is created then deleted is being included in the XML stream which is passed to the session service. This tries to delete an occurrence which does not exist. Service Pack 3
Patch Z036

Tony Marston
23rd September, 2000

mailto:tony@tonymarston.net
mailto:TonyMarston@hotmail.com
http://www.tonymarston.net

Amendment history:

14th August 2002 Updated list of outstanding bugs for Patch Z036.
Re-issued sample code.
27th June 2002 Added diagrams to illustrate how a presentation layer component can obtain and store data via an XML stream.
23rd May 2002 Updated procs in line with the 3 Tier version of my demonstration application.
Re-issued sample code.
29th March 2002 Removed the profile and options arguments from the VERIFY_DELETE operation.
Streamlined code following experiences gained while converting my entire demonstration application to 3 Tier.
Re-issued sample code.
28th Sept 2001 Added changes argument to VALIDATE operation to circumvent the problem caused by remote validation not being supported from a field or occurrence trigger. I now perform getlistitems instead of xmlload and return error messages via the Message Object described in my article Error/Message Handling for self-contained services.
Re-issued sample code.
21st Sept 2001 Removed selectlist argument from PUTDATA and VALIDATE operations.
Re-issued sample code.
17th Sept 2001 Added component U_MULTI1.
Modified sample code so that session services include both the BAM and the PAM entities to allow for the translation of item names in retrieve profiles should they be different between the two models.
3rd June 2001 Updated sample code to deal with situation where a record being amended by one user is amended or deleted by another user.
26th May 2001 Renamed the UI model to the PAM (Presentation Application Model).
Renamed the physical model to the BAM (Business Application Model).
Added operations UCOMMIT and UROLLBACK to session service components.
Re-issued sample code.
9th May 2001 Removed section 5.f DELDATA as this can be done by PUTDATA.
Added candidate key NAT_INS_NO to the PERSON entity to test key validation.
Re-issued sample code.
26th April 2001 Added section 5.d VALIDATEKEY to get around problem with retrieve/reconnect and $occstatus='new'.
Re-issued sample code.

Back to TOP.

counter