The first thing we wanted to implement completely was the procedure for getting a name pattern filtered list of horse names.
So we started by moving the test stub code away from the HorseManager class and into the HorseFetcher class. It was a good thing we did that because it helped us realize that we had chosen too narrow a name for the method in the fetcher class.
The name we first gave this method was simply GetList, and it accepted as its only parameter a name pattern string. We now realized that such limited filtering wasn't going to be good enough. We had to make it possible to filter horse lists in other ways too. We felt we had two options:
Realizing that this wasn't a critical decision, we went for the first of these options.
Why wasn't it a critical decision? Well, because a data fetcher normally has one client only. The only kind of object likely to access a HorseFetcher object is a HorseManager object, at least in the kind of architecture we're talking about now. In this respect, data access objects differ from main business objects. When a developer installs a main business object, making it available to other developers, he or she must assume that the object over time might have many different clients belonging to many different client applications. When that same developer, perhaps a year or more later, needs to modify the class, he or she won't even know about some of these clients. Main business objects are meant to be reused that way. That's why we recommend that clients always access main business objects through separate COM interfaces.
This loose relationship between a main business class and most of its clients makes it nearly impossible to change one of these clients and then change the main business class to suit that particular client. If you try, you're almost guaranteed to destroy the relationship between that main business class and other clients.
The relationship between a main business class and its data access classes is different. They belong to each other. In most cases, you can freely change them to suit each other, just as long as your changes don't affect any of the clients of the main business class.
Given this close relationship between main business and data access classes, we can now state a convenient goal regarding our data access classes. We can concentrate on the task of making sure everything works for the requirements we know about and postpone all discussions of future requirements.
In fact, we could have left things as they were, without changing the name of the GetList method, but we wanted to make it perfectly clear what this method does. So we changed the name from GetList to GetListForNamePattern, which is evident in the following code:
Public Function GetListForNamePattern _ (ByVal strNamePattern As String) As Recordset Dim rs As ADODB.Recordset Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20 rs.Open rs.AddNew rs!HorseId = 10 rs!HorseName = "Nijinsky II" rs.AddNew rs!HorseId = 20 rs!HorseName = "Nearco" Set GetListForNamePattern = rs End Function |
The next thing we needed to do was make the main business object—the HorseManager—call the GetListForNamePattern method in a proper way. This was easy, as the following piece of code proves:
Private Function GetListAsRSForNamePattern _ (ByVal strNamePattern As String) As Recordset Dim objFetcher As RaceDataAccess.HorseFetcher Set objFetcher = CreateObject("RaceDataAccess.HorseFetcher") Set GetListAsRSForNamePattern = _ objFetcher.GetListForNamePattern(strNamePattern) End Function |
With one irritating exception, the code worked fine the first time we tested it. We had moved the test stub code, step by step, from the facade through the main business object and now into the data access class. We had also in successive steps added delegation to the classes involved. Our design and code still worked. The only unfinished step was the final one, and we were ready to take it. What didn't work? We'll tell you because it happens every time we generate code from a Rose model to a new ActiveX DLL project in Microsoft Visual Basic.
Every Visual Basic project has a property named Startup Object as shown in Figure 13-1. When Rose creates a new Visual Basic project, Rose sets the Startup Object of the project to the special Sub Main procedure. In most ActiveX DLLs there's no need for a Sub Main procedure, so we would have liked it much better if Rose could have generated None as the startup object, as in Figure 13-1, or at least given us a chance to set that as an option before code generation. As things are now, we have to modify the property manually in every new project. To be truthful, it's not so much having to set it manually that bothers us. It's that we always forget to do it, making Visual Basic give us nasty messages, as in Figure 13-2.
Anyway, we took up the problem with the Rational guys responsible for the integration of Rational Rose and Visual Basic. They told us they couldn't help it. They would have loved to set the property for us at code generation time, but the Visual Basic API didn't support it.
So we now turn to Microsoft. "Hey, guys! Add support for this in the Visual Basic API, please! That would help not only Rational Rose and Microsoft Visual Modeler but, chiefly, the increasing population of developers who use Rose or Visual Modeler to model their applications!"
Figure 13-1. For every new Visual Basic project generated by Rose, we must manually set Startup Object to None. (Yes, we know that threading and unattended execution must be handled as well. We'll come back to those issues in Chapter 14, "Using Microsoft Transaction Server," when we explain adaptation to MTS.)
Figure 13-2. This is what we don't want!
After checking that we still got the same set of horse names displayed in our test form, even though we had moved the test stub code again, we replaced the test stub code in the HorseFetcher class with real database access code. Our ultimate goal is to have efficient database access code, but we'll start simply. We'll describe enhancements of data access procedures in Chapter 17, "Securing Good Database Performance and Scalability," but here's the code we wrote for the first version of the HorseFetcher class:
Public Function GetListForNamePattern _ (ByVal strNamePattern As String) As Recordset Dim rs As ADODB.Recordset, strSQL As String Const strConn = _ "Provider=SQLOLEDB.1;Server=RACING_SERVER; " & _ "User ID=sa;Initial Catalog=MSPress_ADBRacing" strSQL = _ "SELECT HorseId, HorseName " & vbCrLf & _ "FROM Horses " & vbCrLf & _ "WHERE HorseName LIKE '" & _ "strNamePattern & "' " & vbCrLf & _ "ORDER BY HorseName" Set rs = CreateObject("ADOR.Recordset") rs.ActiveConnection = strConn rs.Source = strSQL rs.LockType = adLockReadOnly rs.CursorLocation = adUseClient rs.CursorType = adOpenStatic rs.Open Set rs.ActiveConnection = Nothing Set GetListForNamePattern = rs End Function |
There are several important points to make about the preceding code:
This point is extremely important. If you want to create a scalable application based on ADO recordsets for data transport, you must disconnect any recordset from the database as soon as it's created, and you must be able to marshal it between processes and machines.
Now things are interesting. We have real database access code, and we're eager to test it. And lo and behold! It worked the first time again, as you can see in Figure 13-3.
Figure 13-3. Now real data from the database, in sync with the name pattern, populates the list box.
This, however, is no surprise. Of course it should work. We've been very careful at every step, and we've made sure that we have good code all the way. The test stub code told us quite clearly what our job was, and since we're no amateurs we did our job and expected it to work. You're a pro yourself, so you too should be able to have this kind of code work the first time you test it.