We decided to use Visual Basic code to implement two of our eight rules in business components. Here's a short recap of these two rules:
Given our decision to implement these rules in business components, in which tier should we put them? We can choose from four different places to put them:
We'll advance two reasons for putting rules in your data access classes or, more specifically, in your modifier classes, which are responsible for all data modifications.
Let's look at both these issues in greater detail, beginning with the matter of network traffic.
If a rule requires queries to be sent to the database to check, for example, whether a horse has already been entered in a race before it's deleted, network traffic can be reduced if the rule is protected in the data access class rather than in the main business class. This is especially true if the data access component but not the main business component is installed on the same computer as the database server. If so, programming the rule in the data access component leads to increased performance and increased scalability of the entire application.
The database is the last outpost; the data access class is the next-to-last outpost. When considering the placement of validation rules, you could think of the database as gold, the data access class as silver, and the main business class as bronze. The facade and the client, in this respect, are also-rans, which means, of course, that they didn't make it into the first three places.
In some installations, you might need a stateful version of a component in addition to a stateless one. The stateful version won't scale nearly as well as the stateless one, but you might need it to fulfill certain requirements. In such a case, you'll have two, or possibly three, main business classes managing horse information:
Those classes will obviously share the same data access classes. If you decide to put your rules in the modifier component, there's no need to duplicate the code; whereas if you put them in the main business classes, you need to add them in at least two places: the HorseManager class and the Horse class. Furthermore, in some cases one modifier component will call another. You'll also come across situations in which a special transaction management object calls several modifier components. In all these cases, further covered in the "Complex Transactions" section of Chapter 23, "Some Final Issues," putting the rules in the proper modifier component helps a lot.
In our view, then, the data access classes—specifically the modifier classes—are by far the best place for all business rules not implemented in the database. This is particularly convenient because it's also the obvious place for checking on messages from the database server about the rules that are executed there.
We'll end this chapter by showing you a component enhanced with business rules error handling. Please keep in mind that the code we show you isn't normative; it's just an example. This is especially true where error handling is concerned; different developers, and different developing organizations, have different approaches to the question of how to set up error handling. What we want to give you is just a very simple example, helping you find out how you should handle your errors and, particularly, attempts to violate your business rules.
Public Sub Save(rs As Recordset) Dim objADOSrvcs As RaceADOSrvcs On Error GoTo SaveErr Set objADOSrvcs = CreateObject("RaceDataAccess.RaceADOSrvcs") rs.MoveFirst Do While Not rs.EOF If rs.EditMode = adEditAdd Then ' Get New IDs: rs!HorseId = objADOSrvcs.GetNewNumber("Horses") End If ' Check for any invalid sex change. If InvalidSexChange(rs) Then Err.Raise Number:=vbObjectError + 101 End If rs.MoveNext Loop ' Do the actual update. rs.MoveFirst rs.ActiveConnection = strConn rs.UpdateBatch GetObjectContext.SetComplete Exit Sub SaveErr: Dim strErrDescr As String Dim cn As Connection GetObjectContext.SetAbort Select Case Err.Number Case vbObjectError + 101 strErrDescr = "Invalid sex change!" Case -2147217900 ' Error from database Set cn = rs.ActiveConnection Select Case cn.Errors(0).NativeError Case 2601 ' Unique constraint strErrDescr = cn.Errors(0).Description Case 547 ' Column foreign key constraint, ' table check constraint strErrDescr = cn.Errors(0).Description End Select Case Else strErrDescr = Err.Description End Select Err.Raise Number:=Err.Number, _ Source:=Err.Source, Description:=strErrDescr End Sub |
As you can see, three sections of this code are printed in boldface type:
This concludes Chapter 22. We hope this chapter has helped give you a better sense of how to handle business rule violations and also of how to decide where you should put your error checking and error handling code. But please handle all errors with great care; if you don't, they'll haunt you. There's no escaping them short of giving up your own ghost.