Visual Basic 6 Business Objects
I've made it very clear, so far, that the business objects are the application, and the user-interface is pretty expendable. Thus it seems quite logical to assume that each object should know how to save itself to the database. By just adding some form of Load and Save methods to each object, it would appear that we've pretty much solved all our persistence issues. Let's consider this approach.
Implementing a Load Method
We'll consider, as an example, the Person object that we developed in the previous section.
Don't actually make any of these changes to the PersonObjects project, however, because this is not yet the optimal solution to our problem. I'll clearly signal when we have found the best solution.
Were we to implement a solution where objects saved themselves, we could add a Load method like this (assuming we have a JET database with a Person table for our data):
Public Sub Load(SSN As String) Dim recPerson As Recordset Dim strConnect As String Dim strSQL As String strConnect = "Provider=Microsoft.Jet.OLEDB.3.51;" & _ "Persist Security Info=False;" & _ "Data Source=C:person.mdb" strSQL = "SELECT * FROM Person WHERE SSN='" & SSN & "'" Set recPerson = New RecordSet recPerson.Open strSQL, strConnect With recPerson If Not .EOF And Not .BOF Then udtPerson.SSN = .Fields("SSN") udtPerson.Name = .Fields("Name") udtPerson.Birthdate = .Fields("Birthdate") Else recPerson.Close Err.Raise vbObjectError + 1002, "Person", "SSN not on file" End If End With recPerson.Close Set recPerson = Nothing End Sub
This code would simply open the database, perform a lookup of the person (based on the supplied social security number), and put the data from the recordset into the object's variables. Also, note that the path we've used when opening our database is an absolute one; you'll need to change it to point to your own database.
Since this code makes reference to a Recordset object, you would need to add a reference in your project to ADO. Using the Project-References menu option you would select the most up-to-date ADO reference, such as Microsoft ActiveX Data Objects 2.0 Library.
Using the Load Method from the UI
From the UI developer's perspective, this would be pretty nice, since all they'd need to do would be to get the user to enter the social security number. Let's assume the UI programmer stored this SSN number in a variable called strSSN. Some simple code to achieve this (which ignores any input validation concerns right now), which could be placed in a button event or the Form_Load routine, might run as follows:
Dim strSSN As String strSSN = InputBox$("Enter the SSN")
Remember that we're not actually making these changes to our Person project and PersonDemo UI, because this is not the optimal solution to our problem.
The working UI code would then follow:
With objPerson .Load strSSN txtSSN = .SSN txtName = .Name txtBirthdate = .Birthdate lblAge = .Age End With
Of course, this code would not only put the values from the object into each field on the form, but it would also trigger each field's Change event. These events are set up to put the values right back into the object, which is a rather poor solution. We could, perhaps, overcome this with a typical UI trick: a module-level flag to indicate that we were loading the data:
Private flgLoading As Boolean
Then, at the top of each Change event, we'd just add the following line of code:
If flgLoading Then Exit Sub
And finally, we'd slightly alter the code that copied the values to the form's fields:
flgLoading = True With objPerson .Load strSSN txtSSN = .SSN txtName = .Name txtBirthdate = .Birthdate lblAge = .Age End With flgLoading = False
Saving the Object's Data through the ApplyEdit Method
Back in the Person object, we already have a method, ApplyEdit, to handle updating the object; so we would just add some code to that routine:
Public Sub ApplyEdit() Dim recPerson As Recordset Dim strConnect As String Dim strSQL As String If Not flgEditing Then Err.Raise 445 flgEditing = False flgNew = False strConnect = "Provider=Microsoft.Jet.OLEDB.3.51;" & _ "Persist Security Info=False;" & _ "Data Source=C:person.mdb" strSQL = "SELECT * FROM Person WHERE SSN='" & SSN & "'" Set recPerson = New Recordset recPerson.Open strSQL, strConnect With recPerson If Not .EOF And Not .BOF Then .Edit Else .AddNew End If .Fields("SSN") = udtPerson.SSN .Fields("Name") = udtPerson.Name .Fields("Birthdate") = udtPerson.Birthdate .Update End With recPerson.Close End Sub
Since this would just update an already existing routine, we wouldn't need to make any changes to our form's code to support the new functionality.
This solution is pretty nice. Probably the biggest benefit of objects that save themselves is that they are very easy for the UI developer to understand. They simply need to have some code to support the Load method, and they're all set.
It's worth noting that the way we would have coded the Load and ApplyEdit methods essentially employed optimistic locking. This means that no lock is held on the data in the database, so many users may bring up the same row of data at the same time.
Since no database connection was maintained once the data was loaded or saved, there was no lock on the data in the database while the object was in memory.
We could very easily modify the code to maintain an open recordset as long as the object existed, thus converting this to use pessimistic locking. This means that a lock would be held on any data that is brought into our objects, preventing more than one user from ever attempting to view or alter the same set of data.
This could become very resource intensive if there were a lot of objects, however, since each object would have to maintain its own reference to a recordset.
There are a couple of drawbacks to this approach of objects saving themselves. One of our primary goals in creating objects is to make them accurate models of the business entities they represent. By putting all the data-handling code into the objects themselves, we've effectively diluted the business logic with the mechanics of data access. While we can't make our objects pure models of the business entities, it's important to keep as close to that ideal as possible.
Another drawback is that we've tied the data handling and the business handling together in a single object. If our intent is to distribute processing following the CSLA then this approach doesn't help us meet that goal. What we really want is to separate the data access code from the code that supports the business rules - so they can be distributed across multiple machines if need be.
On the upside, this technique of objects saving themselves is very good for applications where we know that the solution doesn't need to scale beyond a physical 2-tier setting. If the application will always be communicating with an ISAM database, such as Microsoft Access, or directly with a SQL database, such as Oracle, then this may be an attractive approach. Be warned, however: applications rarely stay as small as they were originally designed, and it may be wise to consider a more scalable solution.
Objects That Save Other Objects
Now that we've discussed how the UI could directly save an object, and how an object might directly save itself, we'll look at how we can design our business objects to rely on another object to manage their data access. This approach has a couple of very important benefits and is the one we'll use to write our video rental store application through the book.
An important benefit of having an object save itself, as in the previous section, is that the UI developer has a very simple and clear interface for loading and saving an object. At the same time, we really don't want the data access code to be located in the object itself, since that doesn't provide a very scalable solution.
One way to design an application's persistence is to create an object that the UI can call when it needs to save a business object to the database. This new object is designed to manage the persistence of the business object, and so it's called an object manager. The object manager contains all the code to retrieve, store, and delete a business object from the database on behalf of the UI.
Another alternative utilizes data-centric business objects. This is basically the concept that we can take objects as we saw in the previous section, "Objects that Save Themselves", and pull only the data-centric processing out into a new object. Then the UI-centric business object can make use of the data-centric business object as needed to retrieve, store, and delete its data from the database.
The object manager solution is a valid approach to designing an application with distributed objects, and so we'll walk through how it could be implemented. Once we've coded this solution, we'll see how easy it is to move from an object manager solution to one using data-centric business objects.
Page 10 of 13