Splitting Up a Traditional Application

Let's take a simple Visual Basic application, like any we may have created in Visual Basic 3.0. An application typically has at least one form for the user-interface, along with some code to do any work that needs to be done, and the application probably talks to a database to store and retrieve data.

The Application

To keep this very simple, let's build a quick Visual Basic application that has a single form and uses a simple text file as a logical database. This example demonstrates the distinct layers of an application.

We'll just have a simple form, as shown below. The text box is called txtName and the command buttons are cmdGet and cmdPut:

Let's suppose that we have two business rules. The first is that the name must be in uppercase, and the second is that the name is required.

To ensure that the name is uppercase, we'll add the following code:

Option Explicit

Private Sub txtName_LostFocus()
  txtName.Text = UCase$(txtName.Text)
End Sub

And to make sure we don't save a blank name, we'll add this code:

Private Sub cmdPut_Click()
  If Len(Trim$(txtName.Text)) = 0 Then
    MsgBox "You must enter a name", vbInformation
    Exit Sub
  End If
End Sub

Now let's just add some code to save the name to a file. This would normally be saving to a database, but we want to keep the program very simple to illustrate the different processes that are happening:

Private Sub cmdPut_Click()
  Dim lngFile As Long

  If Len(Trim$(txtName.Text)) = 0 Then
    MsgBox "You must enter a name", vbInformation
    Exit Sub
  End If
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Output As file
  Print #lngFile, txtName.Text
  Close lngFile
End Sub

Finally, we'll add some code in behind cmdGet to pull the name back in from our 'database':

Private Sub cmdGet_Click()
  Dim lngFile As Long
  Dim strInput As String
  
  file = FreeFile
  Open "C:\TEMP.TMP" For Input As lngFile
  Line Input #file, strInput
  txtName.Text = strInput
  Close file
End Sub

Now run the program. As you can see, we're able to store and retrieve a name. Furthermore, we won't save a blank name, and any name that we do save is stored in uppercase.

Now let's examine the program from the viewpoint of a three-tier logical model. We'll try to determine which parts of the program perform the presentation, which parts the business logic and which parts the data processing.

The Presentation Tier

This is really the user-interface, or how the program presents information to the user and collects information from the user. In our simple example, the presentation layer certainly includes the form itself:

The presentation layer also includes some of the code that we put behind the form, but certainly not all the code is directly related to the presentation. In the following listing, the presentation code is highlighted:

Option Explicit

Private Sub cmdGet_Click()
  Dim lngFile As Long
  Dim strInput As String
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Input As lngFile
  Line Input #file, strInput
  txtName.Text = strInput
  Close file
End Sub

Private Sub cmdPut_Click()
  Dim lngFile As Long
  
  If Len(Trim$(txtName.Text)) = 0 Then
    MsgBox "You must enter a name", vbInformation
    Exit Sub
  End If
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Output As lngFile
  Print #lngFile, txtName.Text
  Close file
End Sub

Private Sub txtName_LostFocus()
  txtName.Text = UCase$(txtName.Text)
End Sub

Out of all that code, almost none of it is involved in actually presenting data to the user. Take a look at the lines that we've highlighted. For instance, we need to display the name after we've retrieved it from storage:

txtName.Text = strInput

Notice, however, that this line is stuck right in the middle of several other lines of code that have nothing to do with presenting the data.

Detecting that txtName is blank is a business rule, so that is not part of the presentation. Notifying the user that it is blank, however, is indeed part of the user interface:

MsgBox "You must enter a name", vbInformation

Again, our presentation code is in the middle of other code that is not interface-related. The upshot of this is that if we want to change the business rule, or we want to change the presentation, then we're going to be changing the same code block for either task.

That wasn't too bad, but the line in the LostFocus event is not so cut and dried. This line does two things: it converts the name to uppercase (a business rule), and it displays the name back into the form so that the user can see the final result (a presentation choice):

txtName.Text = UCase$(txtName.Text)

Looking at these lines of code, you can see just what a mess we've gotten ourselves into. We have no way of changing the interface without tampering with business-related code, and we can't change our business rules without affecting the interface code.

The Business Tier

Let's now look at the parts of the program that handle the business logic or business rules. Again, it's only a part of the overall program, so we'll highlight the areas that we need to examine:

Option Explicit

Private Sub cmdGet_Click()
  Dim lngFile As Long
  Dim strInput As String
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Input As lngFile
  Line Input #lngFile, strInput
  txtName.Text = strInput
  Close file
End Sub

Private Sub cmdPut_Click()
  Dim lngFile As Long
  
  If Len(Trim$(txtName.Text)) = 0 Then
    MsgBox "You must enter a name", vbInformation
    Exit Sub
  End If
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Output As lngFile
  Print #lngFile, txtName.Text
  Close lngFile
End Sub

Private Sub txtName_LostFocus()
  txtName.Text = UCase$(txtName.Text)
End Sub

In the cmdPut_Click event, we need to make sure that the name is not blank - and avoid saving the value if it is:

If Len(Trim$(txtName.Text)) = 0 Then
  MsgBox "You must enter a name", vbInformation
  Exit Sub
End If

Once again, notice how there is code managing the presentation layer right in the middle of our business processing.

The situation is worse (but now rather familiar) within the LostFocus event: we see the same line that we saw in the presentation layer, now working for our business processing to make sure that the name is in upper case:

txtName.Text = UCase$(txtName.Text)

Furthermore, suppose we need to provide access to this name to some other program, or from some other part of the same program: since the business logic is so tied into the display of this form, we'd have to duplicate the business code elsewhere.

Clearly, this is not an ideal way to organise an application.

The Data Services Tier

In our simple example, the data service code (highlighted in the following code) is fairly straightforward:

Option Explicit

Private Sub cmdGet_Click()
  Dim lngFile As Long
  Dim strInput As String
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Input As lngFile
  Line Input #lngFile, strInput
  txtName.Text = strInput
  Close file
End Sub

Private Sub cmdPut_Click()
  Dim lngFile As Long
  
  If Len(Trim$(txtName.Text)) = 0 Then
    MsgBox "You must enter a name", vbInformation
    Exit Sub
  End If
  
  lngFile = FreeFile
  Open "C:\TEMP.TMP" For Output As lngFile
  Print #lngFile, txtName.Text
  Close file
End Sub

Private Sub txtName_LostFocus()
  txtName.Text = UCase$(txtName.Text)
End Sub

Still, in both the cmdGet_Click and cmdPut_Click event code, the data processing is mixed right in with the presentation and business logic, making it difficult to change the data processing without risking the other parts of the program.

The n-tier Solution

Certainly, there are conventional solutions to help deal with these problems - including putting code in BAS modules, or creating class modules to hold this code. Unfortunately, these solutions don't fully meet the needs of code reuse and separating (or partitioning) the various parts of the application.

The ideal solution is to pull the user-interface code into one area, the business logic into another and the data processing into a third. By breaking our application into these tiers, we can make it much easier to change any section of our program with less risk of causing bugs in the other tiers. By avoiding the case where a single routine includes code for both presentation and business logic for instance, we can change our presentation code without impacting the business code itself.

We can also facilitate code reuse. For instance, the code in the business tier may be useful to more than one form in the user-interface tier. If our business code is intermingled with the presentation code, it can be very difficult to use that business logic across various different presentations. By separating the code, we can make it very easy for our application to show the same business information through various presentations.