Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
BulgaraCeha slovacaCroataEnglezaEstonaFinlandezaFranceza
GermanaItalianaLetonaLituanianaMaghiaraOlandezaPoloneza
SarbaSlovenaSpaniolaSuedezaTurcaUcraineana

AdministrationAnimalsArtBiologyBooksBotanicsBusinessCars
ChemistryComputersComunicationsConstructionEcologyEconomyEducationElectronics
EngineeringEntertainmentFinancialFishingGamesGeographyGrammarHealth
HistoryHuman-resourcesLegislationLiteratureManagementsManualsMarketingMathematic
MedicinesMovieMusicNutritionPersonalitiesPhysicPoliticalPsychology
RecipesSociologySoftwareSportsTechnicalTourismVarious

Managing Business Logic -N-Tier System Design

computers



+ Font mai mare | - Font mai mic



Managing Business Logic -N-Tier System Design

One of the new buzzwords of the day is n-tier design. What does this mean and how does it affect your system development work? How many tiers are n? What are those tiers? In this chapter well examine the n-tier design model and understand the advantages it can provide. Well investigate the concept of business logic and see how it differs from data validation. The use of classes dedicated to providing business logic enforcement will be described and you will see how to incorporate these objects into your applications. Finally, well examine the use of ActiveX servers for the business logic objects and see how this can contribute to future evolution of your applications into new technologies.



8.1. What is n-tier system design?

The buzzwords of the past can help us to understand n-tier design. Client-server system design is based on the principle of dividing a data management system into a front end that provides the user interface and a back end that provides the data storage and retrieval. Client-server design could be called 2-Tier design because there are two tiersthe user interface tier and the database tier.

N-tier design simply indicates that there will be more than two tiers. For a while the buzzword was 3-tier design, but the '3' was replaced by 'N' to address designs that had more than three levels. In the 3-tier system design model, the tiers are user interface, business logic, and database. Additional levels considered in n-tier designs can be hardware interface, communications, operating system interface, and others. With a high-level language like Visual FoxPro, you arent often concerned with hardware and operating system interfaces because those responsibilities are handled for you.

In this chapter I will focus on the 3-tier model and especially on the middle tierthe business logic tier. Figure 1 shows the 3-tier design model.

Figure 1. The 3-tier system design model.

Figure 1 shows three blocks for the user interface. This means there can be more than one interface for getting at the data. These interfaces might be your applications forms, a spreadsheet application, and an ad hoc report generator. There could be more than three interfaces. The data storage and retrieval might constitute more than one database, although the diagram shows only one.

The business-logic block in Figure 1 is the focus of this chapter. It can represent a single object or many objects. I'll examine the business-logic layer in more depth for the rest of this chapter.

8.1.1. What is business logic?

Business logic, which is sometimes referred to as business rules, comprises the set of rules that describes the storage and retrieval of data within the context of a specific business. Business logic cannot be defined in a vacuum; it requires heavy input from the experts within the business itself. You, as a developer, can implement business-logic enforcement, but your discipline cannot define the rules. You need the consultation of experts in the business, for whom the system is being built, in order to successfully define the business logic. These people are called business domain experts.

To understand the concept of business logic, lets examine a real-world example. Using the time and billing example database from the download files, you will examine an imaginary consulting firm. This firm has employees who work on projects for clients. In talking with the management of the company, you are told that employees are restricted to seeing data only regarding the projects that they are assigned to. This issue of restricting the display of data to only those projects for a particular employee is an example of a business logic rule. You would never know this from studying the database design. Even reviewing the validation rules for the data wouldnt reveal this simple but important rule.

In further discussions with management, you find out that the business considers a project to be an assignment of an employee to a client for a particular short-range or long-range task. Management tells you that the people in the office always want to see projects this way, with the client and the employee identified. Project information is meaningless to them unless they can see the client and employee information as well. This is another issue that can be enforced through the business logic rules.

8.1.2. How is business logic different from data validation?

You can see from the issues above that business logic encompasses a set of rules about how the business sees its data. Wouldnt some of these issues be dealt with in the data-validation rules related to the data-domain definitions? To see how these business-logic rules are different from data-domain definitions, lets look again at the two examples.

If the first example, where employees can see only their projects, were to be mapped to database design, it would likely cause you to design multiple tables for projectsone table for each employee. Although this might help to enforce of the business rule, it would certainly complicate the process of reassigning a project. It would also introduce a level of serious complexity in dealing with an employee leaving the company and being replaced by a new one. It makes much more sense to manage this issue dynamically at the time that data is displayed.

You could handle the second situation through your form design and put the burden on the user interface. Although this might seem like a good solution, it has its problems in long-term maintenance of the system. It also limits the tools that the users can leverage in managing their data. The form approach to managing data does not allow the data to be accessed from other applications like Excel or a VB application without duplicating the data-management code.

Businesses are dynamic, changing over time. Your design should allow for relatively easy adaptation to changes in the business. By moving the business logic out of the user interface and the database, you allow for adapting to changes in the business logic without requiring major overhauls of the entire system.

Another area where business logic is different from data validation can be found in the very rules you apply to the field-domain definitions. In the example database, the Clients table has a field named cPostalCode, which is a character field with a width of 20 characters. In your meetings with the management of the company, they explained that they only service clients in three states: Washington, Oregon, and Northern California. Due to international labor regulations they do not handle any clients in Canada. Okay, so what do you do with this information? Do you put a validation rule on the cPostalCode field in the database that limits the values in that field to only those three states?

If you put this constraint on the field in the database, you create an inconsistency in the data definitions. The field is defined as storing the postal code for the location of a client. Postal codes cover the entire world. Your validation rule would restrict the postal codes to only part of the world. Any future change in the companys business practices would require a redefinition of the fields domain and changes to the database itself.

It would be much better in the long run if you define the fields domain based on the entity without regard to the business practices of the company, and implement the business rule through the business-logic layer of the system. This would mean implementing a field-validation rule that would allow any postal code to be entered in the table, and using a business-logic rule that restricted the postal codes to those three states. With this design, if the company ever dealt with the international work regulations and wanted to start marketing its services in Canada, your system could handle it by making a change in the business logic. No changes to the database design would be required.

8.2. Building business logic enforcement classes

In the earlier sections of this chapter you learned about the nature of business logic. In this section you will begin the design of your business-logic class hierarchy.

8.2.1. Abstraction of business logic into a class design

Abstraction is the process of describing a complex problem in simple terms. The abstraction of business logic as a requirement in your systems starts with designing a top-level class for all business-logic classes. You need to list the general requirements for a business-logic class. Table 1 is a partial list of those requirements.

Table 1. The essential design requirements for your business-logic base class.

Requirement

Description

Present data

The programmers interface of a business class must be capable of presenting data to the user-interface objects in a fashion that enforces the business-logic rules and eliminates the need for the user-interface layer to store any knowledge of those business-logic rules.

Process data

The business-logic class must present programmers interface components that allow for the processing of data to enforce the business-logic rules during data update, insert, and delete operations.

Allow for specialization on subclasses

The base business-logic class must allow for easy subclassing for specializing to implement specific business logic rule sets.

Lets design a business-logic class for the project entity as it was described above. This business class will begin with an abstract class definition as the base business class. Youll use a form as the base class so you can take advantage of a private data session to isolate the business objects data from any other data that may be in use.

The purpose of the business class is to manage access to the data and enforce business-logic rules in the process. Therefore, youll find that using business-logic objects in your applications will preclude the use of other data-management approaches. You might successfully build an application that mixes business-logic objects with other data-management approaches, but it would be quite difficult to maintain. The maintenance problems would be related to the fact that there are multiple methods in use for managing access to the data. A much better design would be to decide which way to manage data and stick with that approach throughout the application.

Lets get on to describing the base business class. In the project named Chapter8, youll find a class library named Business. This class library holds two class definitions, busBase and busProject. Your base business class is busBase. This class is a subclass of the form with the properties and methods added to it. The properties and methods are listed in Table 2 and Table 3, respectively.

Table 2. The properties of your busBase class.

Custom
Property

Description

CurrentView

Contains a value that represents the current view of the business data. A business object may present data that can be seen in different 'views,' such as the Project business object, which can be seen as client projects or as employee projects. This property will track the current 'view' of the data in the business object.

DataPath

The path for finding the database and tables.

Table 3. The custom methods of the busBase class.

Custom Method

Description

SetUpDataView

Moves the business objects data to a requested 'record.' Not coded in the base class due to the fact that the code is totally dependent on the detailed nature of the business object.

GetFields

Returns a string of field names (including the cursor alias) delimited with commas for the business data. Not coded in the base class.

GetFldData

Returns the current value for a specific field. Not coded in the base class.

GetNext

Moves to the next or previous record in the business data. Not coded in the base class.

OpenData

Opens the data for the business object. Not coded in the base class because it is coded differently for each subclass.

PutFldData

Puts a value into a field in the business data. Accepts two parameters: the alias.fieldname and the value to be put into the field. Returns a numeric value of 1 (successful), -1 (bad field name), -2 (data type mismatch), -3 (business rule violated), and -4 (bad alias name).

RevertData

Issues TableRevert for each cursor open in the business object.

SaveData

Issues TableUpdate for each cursor in the business object. The updates are wrapped in a transaction so it will be an all-or-none proposition. The ValidateRecord method is called for each alias. Returns .T. if successful and .F. if unsuccessful.

ValidateField

Called for each PutFldData operation. Accepts two parameters the alias.fieldname and the value to be tested. Can return logical, .T. for OK and .F. for bad data value. Not coded in the base class.

ValidateRecord

Similar to ValidateField except it is a record-level validation. One parameter is passedthe alias name to be validated. Not coded in the base class.

The three methods that contain code in the busBase class are PutFldData, RevertData, and SaveData. Listing 8-1 shows the code for PutFldData.

Listing 8-1. The code for PutFldData.

LPARAMETERS pcField, pxValue

* Puts data into a field

* pcField is the alias qualified field name

* pxValue is the expression to be put into the field

* Calls ValidateField to check the validity of the field's value

* Returns 1 for success

* -1 for invalid field name

* -2 for data type mismatch

* -3 for rule violation

* -4 Invalid alias in the field name

LOCAL lcAlias, lcUpdateAlias

IF TYPE(pcField) = 'U' OR NOT '.' $ pcField

* Invalid field name

RETURN -1

ENDIF

IF TYPE('pxValue') <> TYPE(pcField)

* Data type mismatch

RETURN -2

ENDIF

IF NOT THISFORM.ValidateField( pcField, pxValue )

* Invalid data

RETURN -3

ENDIF

* Save the currently select alias

lcAlias = ALIAS()

* get the alias portion of the field name

lcUpdateAlias = LEFT( pcField, AT('.',pcField) -1 )

* Check to see that the alias of the field is valid

IF NOT USED( lcUpdateAlias )

RETURN -4

ENDIF

* If we have a valid alias select it

SELECT ( lcUpdateAlias )

* Update the field in the buffer

REPLACE (pcField) WITH pxValue

* Reselect the alias we had at the start of this method

IF NOT EMPTY( lcAlias )

SELECT ( lcAlias )

ELSE

SELECT 0

ENDIF

* Return success

RETURN 1

This code shows the RevertData method.

* Tablereverts all alias'

lnHowMany = AUSED( laAlias )

IF lnHowmany = 0

RETURN

ENDIF

FOR lnCnt = 1 TO lnHowmany

IF CursorGetProp('BUFFERING',laAlais(lnCnt,1)) > 1

TableRevert( .T., laAlias( lnCnt, 1 ) )

ENDIF

ENDFOR

RETURN

Finally, the SaveData method code for the busBase class:

* Tableupdates the alias's in a transaction

* Returns .T. for success and .F. for failure and reversion

LOCAL laAlias(1,2), llRollback, lnHowmany, lnCnt

* Get the number of alias' open

lnHowMany = AUSED( laAlias )

IF lnHowmany = 0

* If there are no alias' open get out

RETURN

ENDIF

* Work the alias'

FOR lnCnt = 1 TO lnHowmany

* Call the ValidateRecord for this alias

IF NOT THISFORM.ValidateRecord( laAlias( lnCnt, 1 ) )

* Failed validation

* Revert the data

THISFORM.RevertData()

* Get out

RETURN .F.

ENDIF

ENDFOR

* Start a transaction

BEGIN TRANSACTION

* Work the alias'

FOR lnCnt = 1 TO lnHowmany

* TableUpdate this alias

IF NOT TableUpdate( 1, .F., laAlias( lnCnt, 1 ) )

* Failed update

* Set rollback variable

llRollBack = .T.

* End the loop

EXIT

ENDIF

ENDFOR

IF llRollBack

* If the rollback variable is set

ROLLBACK

* Revert the buffers

THISFORM.RevertData()

ELSE

* No rollback then commit the transaction

END TRANSACTION

ENDIF

* Return the result

RETURN NOT llRollBack

The code in these methods is simplified to demonstrate the ideas involved. Any one of the methods could be expanded to handle additional behavior. For example, the SaveData method could be expanded to detect the reason why a table update failed and recover from those situations where recovery is possible. Expanding these methods for additional functionality is left as an exercise for the reader. Here, the purpose is to understand the nature of business classes and how you can build them.

Building a subclass

To demonstrate how to customize a business class, try building a project business class that enforces the following rules: Projects are only seen as related to either clients or employees, and the project size limit in your application is $100,000. For this example youll use the Time and Billing data supplied on the books CD.

The methods and events that have code added to them in your busProject class are Init, OpenData, Load, SetUpDataView, GetFldData, GetNext, and ValidateField. The code below shows the Init event for your busProject class.

SET MULTILOCKS ON

The following code shows the OpenData method:

* Open the tables and set the buffering mode for the project data
USE DataEmployees ORDER PrimaryKey ALIAS Employees IN 0

SELECT Employees

CursorSetProp('BUFFERING',5)

USE DataClients ORDER PrimaryKey ALIAS Clients IN 0

SELECT Clients

CursorSetProp('BUFFERING',5)

USE DataProjects ALIAS Projects IN 0

SELECT Projects

CursorSetProp('BUFFERING',5)

The Init and OpenData methods simply open the tables required and set their buffering mode to optimistic table buffering. Following is the code in the Load event for your class:

SET DELETED ON

SET TALK OFF

This code sets a few environment options for the class. Listing 8-2 shows the SetUpDataView code.

Listing 8-2 . The SetUpDataView method code.

LPARAMETERS pcView, piId

* pcView = 'CLIENTS' or 'EMPLOYEES'

* piId = The PK for the client or employee to get projects for

IF EMPTY(pcView)

* No view supplied

RETURN .F.

ENDIF

IF pcView = 'CLIENTS'

* We want the client view

IF THISFORM.CurrentView <> 'CLIENTS'

* If we are not already seeing the Client

* view of the data set it up

SELECT Employees

SET RELATION TO

SELECT Projects

SET ORDER TO ClientId

SET RELATION TO iEmployeeID INTO Employees

SELECT Clients

IF EMPTY( piId )

SET KEY TO iClientID

ELSE

SET KEY TO piId

ENDIF

SET RELATION TO iClientID INTO Projects

LOCATE

ENDIF

ELSE

* We want the employee view

IF THISFORM.CurrentView <> 'EMPLOYEES'

* If we are not already seeing the Employee

* view of the data set it up

SELECT Clients

SET RELATION TO

SELECT Projects

SET ORDER TO EmployeeId

SET RELATION TO iClientID INTO Clients

SELECT Employees

IF EMPTY( piID )

SET KEY TO iEmployeeID

ELSE

SET KEY TO piId

ENDIF

SET RELATION TO iEmployeeID INTO Projects

LOCATE

ENDIF

ENDIF

* Set the current view property

THISFORM.CurrentView = pcView

RETURN .T.

The SetUpDataView method simply sets up the data relations and cursor orders to provide the requested 'view' of the data. This code uses tables from the database directly. The code could just as easily use local views or even remote views for its data (see Chapter 5 for more on views). Also notice that the code above uses the SET KEY command on the master table to restrict the child table to only the records for a given key. You might think of using a SET FILTER, but SET KEY is much faster, even if the filter condition is fully optimizable.

Here is the GetFldData method for the busProject class:

LPARAMETERS pcField

IF EMPTY(pcField)

* No field supplied

RETURN .NULL.

ENDIF

RETURN EVALUATE(pcField)

The code listed above is very simple, returning the contents of a field passed as a parameter to the method. This code could be much more complex, depending on the business object you are creating. Perhaps youd need to make calculations or requery views before returning the field value.

Listing 8-3 is the code that moves the record pointer.

Listing 8-3. The GetNext method code

LPARAMETERS pcWhat, pnHowMany

* pcWhat = 'PROJECT' or empty indicating next master record

IF EMPTY( pcWhat )

* Force pcWhat to character

pcWhat = ''

ENDIF

IF EMPTY( pnHowMany )

* Default to 1

pnHowMany = 1

ENDIF

LOCAL lnRecno, llRet, lcAlias

IF EMPTY(THISFORM.CurrentView)

* If SetUpDataView has never been successful

RETURN llRet

ENDIF

* Save current alias

lcAlias = ALIAS()

IF THISFORM.CurrentView = 'CLIENTS'

* If we are in client view

IF UPPER(pcWhat) = 'PROJECT'

* If the request is for the next project

SELECT Projects

IF NOT EOF() AND NOT BOF()

* If we are not at eof or bof

lnRecno = RECNO()

* move in the direction requested

SKIP pnHowMany

IF EOF() OR BOF() OR iClientID <> Clients.iClientID

* If hit eof or bof or the project is not for the current client

GOTO lnRecno

ELSE

* We've got one

llRet = .T.

ENDIF

ENDIF

ELSE

* If the request was the next client

SELECT Clients

* Shut off the set key

SET KEY TO

IF NOT EOF() AND NOT BOF()

* If we are not at eof or bof

lnRecno = RECNO()

* move in the direction requested

SKIP pnHowMany

IF EOF() OR BOF()

* if we hit eof or bof

GOTO lnRecno

ELSE

* We've got one

llRet = .T.

ENDIF

ENDIF

* Reset the set key

SET KEY TO iClientID

ENDIF

ELSE

IF UPPER(pcWhat) = 'PROJECT'

SELECT Projects

IF NOT EOF() AND NOT BOF()

lnRecno = RECNO()

SKIP pnHowMany

IF EOF() OR BOF() OR iEmployeeID <> Employees.iEmployeeID

GOTO lnRecno

ELSE

llRet = .T.

ENDIF

ENDIF

ELSE

SELECT Employees

SET KEY TO

IF NOT EOF() AND NOT BOF()

lnRecno = RECNO()

SKIP pnHowMany

IF EOF() OR BOF()

GOTO lnRecno

ELSE

llRet = .T.

ENDIF

ENDIF

SET KEY TO iEmployeeID

ENDIF

ENDIF

* Reselect the alias

IF NOT EMPTY( lcAlias )

SELECT ( lcAlias )

ELSE

SELECT 0

ENDIF

* Return the result

RETURN llRet

The GetNext method allows the developer to request a movement in either the master or child cursor. The master is either the Clients or the Employees cursor, depending on the current view, and the child is the Projects cursor. The movement can be any number of records either forward or backward. This functionality is provided through the two accepted parameters: pcWhat and pnHowMany.

The first parameter, pcWhat, is either blank or 'PROJECTS.' If it's blank, the master alias for the view is used. If pcWhat is 'PROJECTS,' then the movement will be in the Projects alias. The second parameter, pnHowMany, is a number indicating the number of records to move. A positive number moves forward and a negative number moves backward.

The code for the client view movement is commented to describe each part of the operation. The employee view is essentially the same except it uses the employee alias instead of the client alias. The method returns either .T. or .F., indicating success or failure, respectively.

The following code shows the ValidateField method for the busProject class, and it implements the limitation on the billing estimate for the project to $100,000:

LPARAMETERS pcField, pxValue

* Validates field values according to the business logic rules

LOCAL llRet

llRet = .T.

DO CASE

CASE pcField = 'Projects.yProjectTotalBillingEstimate'

* Now allowed to exceed 100,000

IF pxValue > 100000 OR pxValue < 0

llRet = .F.

ENDIF

ENDCASE

RETURN llRet

Again, this code is greatly simplified from what you might see in a real business-class definition, but it does demonstrate the idea of validating on a field-by-field basis. In any real situation, the DO CASE construct would probably be much larger to handle the many fields requiring validation. If the validation code for any one field is complex enough, it might be implemented in a custom method of its own that is called from this method.

8.2.2. Implementing business-logic classes into an application

To demonstrate how you might put these business logic classes to use, I built a form named ClProj in the Chapter8 project. This form is based on your frmData class from Chapter 7 and uses the busProject business class as its data source. The data environment for ClProj is empty because the business class provides all data. In Chapter 7 you built a form class that managed the data. In that situation you used the forms data environment. Here you are building an n-tier system, one requirement of which is that the tiers you build be independent of each other. If you were to use and depend on the forms data environment in any way, youd preclude the design from being used when the user interface is not Visual FoxPro. So here you dont use the forms data environment.

There is code in a number of methods and events of the form. The code listed below shows the code in the forms Load event.

THISFORM.BusObj = NewObject('busProject','VCXBusiness.vcx')

THISFORM.BusObj.SetUpDataView('CLIENTS')

This code first creates an instance of the busProject class. It then calls the busProject objects SetUpDataView method, requesting the Client view of the data. Notice that you are storing the reference to the business object in a form property named BusObj. Using the BusObj property to hold the reference to the business object allows you to easily refer to the business object form with any code in any method of the form, or any objects in the form.

This line of code shows the Init of the form:

THISFORM.RefreshData()

The Init is calling a form method named RefreshData, which is a custom method that you have added to this form. The following code shows the code in the RefreshData method:

WITH THISFORM

.txtBase1.Value = .BusObj.GetFldData('Clients.cCompanyName')

.txtBase2.Value = .BusObj.GetFldData('Projects.cProjectName')

.txtBase3.Value = .BusObj.GetFldData('Employees.cLastName')

.txtBase4.Value = BusObj.GetFldData('Projects.yProjectTotalBillingEstimate')

ENDWITH

This method sets the value properties of various textboxes in the form to the value returned from calls to the business objects GetFldData method. The next methods you will examine are two of the frmData classs methods and two new ones you have added to this form. These are the GoNext, GoPrevious, GoNextProj, and GoPrevProj methods, respectively. Here is the GoNext method:

IF THISFORM.busObj.GetNext()

THISFORM.RefreshData()

ELSE

MessageBox( 'This is the last client record', MB_OK+MB_ICONINFORMATION, 'Last Record')

ENDIF

Here is the GoPrevious method:

IF THISFORM.busObj.GetNext('', -1)

THISFORM.RefreshData()

ELSE

MessageBox( 'This is the first client record', MB_OK+MB_ICONINFORMATION, 'First Record')

ENDIF

Notice the simplicity of the code in these two methods. They call the business objects GetNext method, passing the numeric argument for the proper movement. If GetNext fails, the forms code displays a message box to the user. An important thing to note here is that none of the code in the business class definitions ever displays any messages to the user.

This is because the business classes are data managers and therefore they have no responsibility to interact with the user. It is the responsibility of the form, which is the user interface object, to provide the interaction with the user.

Here is the GoNextProj method:

IF THISFORM.busObj.GetNext('PROJECT')

THISFORM.RefreshData()

ELSE

MessageBox( 'This is the last project record for this client', ;

MB_OK+MB_ICONINFORMATION, 'Last Record')

ENDIF

And here is the GoPrevProj method:

IF THISFORM.busObj.GetNext('PROJECT', -1)

THISFORM.RefreshData()

ELSE

MessageBox( 'This is the first project record for this client', ;

MB_OK+MB_ICONINFORMATION, 'First Record')

ENDIF

These last two methods are similar to the previous two, except for the first argument to the GetNext method and the text of the messages.

The click methods of the command buttons call the respective form methods listed above. Listing 8-4 shows the valid event for the textbox, which displays the estimated dollars.

Listing 8-4. The Valid for the textbox showing estimated dollars.

LOCAL lnReturn, llRet, lcMessage

IF THISFORM.ReleaseType > 0

* If the user is exiting the form discard the edit

RETURN

ENDIF

lcMessage = ''

lnReturn = ;

THISFORM.BusObj.PutFldData('Projects.yProjectTotalBillingEstimate', THIS.Value)

DO CASE

CASE lnReturn = 1

* Success

llRet = .T.

CASE lnReturn = -1

* Bad field name error

lcMessage = 'PROGRAM ERROR - Projects.yProjectTotalBillingEstimate ' + ;

'is not a valid field name'

CASE lnReturn = -2

* Data type mismatch

lcMessage = 'PROGRAM ERROR - Data type mismatch for field update'

CASE lnReturn = -3

* Rule violation

lcMessage = 'Estimated $ has an invalid value'

CASE lnReturn = -4

* Invalid alias

lcMessage = 'PROGRAM ERROR - The alias name for the field is invalid'

OTHERWISE

* Unknown error

lcMessage = 'PROGRAM ERROR - error nature is unknown'

ENDCASE

IF NOT llRet

Messagebox( lcMessage, MB_OK+MB_ICONEXCLAMATION, 'Data error')

THISFORM.BusObj.RevertData()

THISFORM.RefreshData()

ELSE

THISFORM.BusObj.SaveData()

ENDIF

RETURN IIF(llRet,llRet,0)

Figure 2 shows the form as its running.

Figure 2. The ClProjX form.

8.2.3. Whats the point?

This chapter has discussed the design and programming of business-logic classes. The objects created from these classes provide data-management and rule-enforcement functionality. If Visual FoxPros database can enforce rules and the forms data environment can provide data access, why do you need these business-logic objects?

The accurate answer to the question is that you dont need them. Data access and rule enforcement can be accomplished without business-logic objects. However, using business-logic objects encapsulates the data access and rules enforcement in a layer that is separate from the database or the user interface. This separation provides the ability to modify an application to use a different interface application without affecting the database, and to change the database without affecting the interface.

In object-oriented design terminology, you have to provide a consistent interface between the database and the user interface through the business-logic object. You can change the interface or the database being used by simply making changes to the business-logic objects code.

Given this 'feature' of business-logic objects, there is a flaw in what youve done in these exercises so far. That flaw is that the user-interface layer of this design is restricted to Visual FoxPro. Although the business objects do make the coding of your forms consistent, they do not allow you to use a different application for the interface layer.

The next section will address this weakness and provide business classes that can be used even when Visual FoxPro is not the user-interface application.

8.2.4. Using ActiveX servers for business-logic objects

Visual FoxPro can build ActiveX servers that contain your classes. These servers will expose your classes so they can be created using applications other than Visual FoxPro. A complete discussion of ActiveX servers is beyond the scope of this book, so this discussion will address only those issues that have a direct effect on your goal.

The first thing to do is create a new project named Chap8x to hold the classes for your server DLL. A copy of this project is in the download files. Your project contains the business class library. You need to edit each of the two classes and set them to be OLEPublic classes (using the Class Info dialog on the Project menu). Figure 3 shows this dialog for the busBase class.

Figure 3. The Class Info dialog for the busBase class.

Once the classes have been made OLEPublic, you have to build the DLL. In the project, click the Build button and select the Build OLE DLL option button. Figure 4 shows the build dialog set up in the proper way.

Figure 4. The ClProj form.

The process of building the DLL builds a DLL file and all of the required associated files, and then registers the DLL on the machine. If you distribute this DLL to other machines, youll need to register it on those machines before it can be used.

Lets use your server

In the project Chapter8 there is a second form named ClProjX. If you open this form for editing and look at the Load event, youll see the following code:

THISFORM.BusObj = CreateObject('Chap8X.busProject')

THISFORM.BusObj.SetUpDataView('CLIENTS')

The difference between this form and the ClProj form is the code used to create the busProject object. Here, the class used is chap8x.busproject, which refers to your ActiveX server. All of the remaining code in this form is exactly the same as in the ClProj form. The code doesnt need to be changed because the methods of the business object have the same names. The only difference is that one is a native VFP object and the other is an ActiveX server object.

Wheres the beef?

You now have your busProject class built as an ActiveX server. The major advantage of creating the business class in an ActiveX server is that you can now use this object in applications other than Visual FoxPro. To demonstrate this, try creating an Excel macro that uses your busProject class to create an object and then fills the cells in the Excel worksheet with data from your Visual FoxPro tables.

Listing 8-5 shows the Excel Visual Basic for Applications macro code.

Listing 8-5

Dim oProject As New chap8x.busproject

Sub project()

With ActiveSheet

.Cells(1, 1).Value = oProject.SETUPDATAVIEW('CLIENTS', '')

.Cells(1, 1).Value = 'Client'

.Cells(1, 2).Value = RTrim(oProject.GETFLDDATA('Clients.cCompanyName'))

.Cells(2, 1).Value = 'Employee'

.Cells(2, 2).Value = RTrim(oProject.GETFLDDATA('Employees.cLastName')) + ',

' +RTrim(oProject.GETFLDDATA('Employees.cFirstName'))

.Cells(3, 1).Value = 'Project'

.Cells(3, 2).Value = RTrim(oProject.GETFLDDATA('Projects.cProjectName'))

.Cells(4, 1).Value = 'Estimate'

.Cells(4, 2).Value = oProject.GETFLDDATA('Projects.

yProjectTotalBillingEstimate')

End With

End Sub

Note that the line getting the employees name should be one line of codenot two as formatted above. The two-line formatting was used here to fit the listing in the available space. The code in this macro first declares a new object from your busProject class in the chap8x.dll that you created earlier. Then a subprocedure is created that first sets up the view of the data and then calls the GetFldData method of your object to read data and write it to certain cells of the currently active Excel worksheet.

This Excel file is named Chapter8.xls in the download files. Figure 5 shows the Excel worksheet after you have chosen Tools Macros, highlighted your macro, and clicked Run.

Figure 5. The Excel worksheet after using

your busProject class to get data.

8.3. Summary

In Chapter 7, you built data-management classes for use exclusively within Visual FoxPro. These classes represented one way to approach accessing data. In this chapter you were introduced to a new way to handle data by using business-logic objects. These objects added some new dimensions to the data-management process. Among the new features was enforcement of logical rules, in addition to the database-integrity rules and presenting data in accordance with the natural way that a business sees its information.

The business classes you built were, admittedly, only demonstrations of the ideas. They were not complete business-logic classes; however, they did show you some ways of accomplishing the goals involved.

With these classes, you saw a mechanism of separating the user interface from the business logic and the business logic from the data. Using a form, you could easily change the data source to Microsoft SQL Server or Oracle by making changes only to the business logic classesand not to the form itself.

Then you took it all one step further by producing your business classes as an ActiveX server. This allowed you to use the same business logic in both Visual FoxPro and Excel. Microsoft Visual Basic, Microsoft Access, Borland Delphi, or any other application or development language that supports ActiveX servers could use this same busProject ActiveX server.

If you think about the last two chapters for a while, youll begin to notice that there are advantages and disadvantages to either approach. The approach in Chapter 7 suffers from being limited to Visual FoxPro, only because there is a dependence on FoxPros data environment and its data-binding capabilities. However, the Chapter 7 approach has the advantage of letting you work with the visual designers within Visual FoxPro, and that can reduce your development time considerably.

The Chapter 8 approach has the advantage of providing a degree of independence between the user interface and the data storage. The N-Tier design model allows you to use the same data with different front ends. One disadvantage of the N-Tier model is that the use of the Visual FoxPro developers interface is somewhat restricted when it comes to dealing with the data.

So, what do you do? Which approach should you use? The answers to those questions lie in a pragmatic review of the requirements of the application you are building. You should know how to build both models and choose the one that is best suited to the application at hand. It doesnt make sense to build an N-Tier model, with all of its inherent flexibility, if the application will never use any other interface than Visual FoxPro forms and never access data stored in any other database. Its equally nonsensical to build a model that only supports Visual FoxPro tiers, if that information will need to be accessed by Microsoft Excel or some other application.

The conclusion: Build the model that solves the problem at hand.



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 959
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved