Designing for Scalability with Microsoft Windows DNA (DV-MPS Designing)
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:
- The only sex change allowed is from male (h) to gelding (v). All other sex changes are physically impossible, and they shouldn't be allowed in code or in stable.
- A horse that has started in a race shouldn't be deleted but deactivated. Therefore, the delete operation must check for membership of a horse in the RaceEntrant table and possibly change the delete operation into an update.
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:
- In client code. We definitely don't recommend that. If you put rules like these in client code, the rules will apply to that client only. All other clients must have the same rules programmed, and they must all have them programmed the same way or at least in consistent ways. If a rule changes, you must find all occurrences of the rule. Then you must correctly change every occurrence of the rule, perhaps in several different applications. On top of that, you must also make the new version of each modified application available (perhaps by force) to every single user of the applications. Obviously, in many cases you can't succeed with such a scheme. The client application is the worst possible place for important business rules.
- In facade classes. This isn't as bad as putting them in client objects, but nearly as bad. Don't do it!
- In the main business class. That's a very good place to put them, and from the comments and questions we get from developers during our seminars and courses, the place most developers would prefer. But we're not quite certain that it's the best place.
- In your data access classes, which is almost, but not entirely, the same as putting them in your main business classes. There are certain advantages in putting them in your data access classes rather than in your main business classes. We'll talk about these advantages next.
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.
- Network traffic can be reduced if you put your rules in a data access class.
- More than one main business class might use the same data access class. You might also have one data access class require services from another data access class.
Let's look at both these issues in greater detail, beginning with the matter of network traffic.
The network traffic issue
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.
The more than one business class issue
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:
- A stateless HorseManager class, like the one we've introduced in this book
- A stateful Horse class, managing and keeping state for one Horse object only
- A stateful Horses class, managing and keeping collections of any number of Horse objects
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.
Enhanced Components
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:
- The first calls the GetNewNumber method in the objADOSrvcs object, implementing the rule about unique IDs.
- The second section in boldface type calls the InvalidSexChange method to find out whether any horse included in the recordset has been subject to an invalid sex change. If so, the method raises an error, which, like any error, will be handled by the error manager further down in the code.
- The third section in boldface type is part of the error manager. It identifies the error (in an overly simplistic way) from the different kinds of rule validations and reports it. This example just shows the way. For a real application, you'd have to add more code to manage each kind of error. Among other things, you'd have to:
- Take a loop through the errors collection of the ADO Connection object to find out whether there's more information to report back
- Decide what kind of error message to send back to the client
- Save as much information about errors as possible in a digital log to allow technical personnel to follow them up
- Possibly create separate error handling routines
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.