An n-tier Architecture

So far, we've looked at a three-tier architecture and how we can split our business processing into two distinct parts: UI-centric and data-centric. What we've really done is move from a three-tier architecture to an n-tier architecture. The new architecture consists of the following four layers:

If you look back at the three-tier diagram in Chapter 1 and compare it to the following figure, you'll note that all we've done is split the business tier into two parts:

This may seem like a small change, but in reality it is very significant. By splitting the UI-centric processing from the data-centric processing, we've given ourselves much more flexibility. We can now keep the UI-centric processing on the client, so we can provide a rich interface for the user. At the same time, we have the option of moving the data-centric processing away from the client to a machine with faster access to the database, so we can gain significant performance benefits.

Let's go through each part of this architecture and see how it works. We'll be using this architecture throughout the remainder of the book, so we need to make sure it's clear in our minds as we go forward.

Presentation Tier

To start off, we have the most visible part of the whole application: the user-interface. Traditionally, these are the forms, menus, and controls that the user uses to interact with our program. It's quite reasonable, however, that we might need to expand this definition to fit into today's environment. The presentation layer for our application might not just be made up of Visual Basic forms anymore. Rather, it might be an HTML page, a spreadsheet, a word processor, or another entire application.

The key thing to keep in mind about the user-interface is that it is not the application. As we'll see shortly, the business objects are the application; the user-interface is just how the user chooses to interact with our objects.

The goal is to create programs that are independent of the presentation to the user. This means that our application can have many different user-interfaces implemented in different tools and technologies.

As we discussed earlier, in a typical Visual Basic application almost all the code, and therefore almost all the processing, lives in the user-interface. Programmers put most of their code behind controls on their forms. Other programmers put the code in code modules to be called from the forms, but really the processing is still entirely centered around the interface:

This is an ideal technique when we're writing event-driven programs, and so it has worked well for us so far. The events come from the user-interface, and so it makes perfect sense for all the code to be directly linked to that interface.

Unfortunately, this approach doesn't work nearly so well when we're trying to develop an object-oriented model for our program, since the focus of the program is on the interaction between objects rather than on an interaction with events in the interface.

Our users often want more than just a traditional user-interface; they may also want a web browser interface, for instance. A more advanced user might even want access to our application from Visual Basic for Applications (VBA), so they can work with the application from within their spreadsheet or word processor.

Also, the part of an application that typically changes the most is the user-interface. Users like to tweak their interface almost continually, and so it seems as if there is a constant stream of requests to move the controls around or replace radio buttons with a combo box and so forth. If our code is primarily placed behind the controls on forms then even an apparently simple interface change can be complex. Not only do we have to change the interface, but we probably have to rearrange the actual business logic to compensate for the changes.

This is not an easy request to fulfil if we have a substantial amount of code tied to our Visual Basic form interface. Even putting much of the code in BAS modules doesn't help, since neither Excel nor browser-based interfaces will have easy access to the code.

On the other hand, if our program is built based on components that contain business objects then we can easily write a flexible interface, or even a number of interfaces, to talk to our application:

Changes to a Visual Basic form interface now become much simpler, and safer, since our business code is independent of the code inside the form.

Also, our application automatically supports any program that can act as a client to an ActiveX server. This includes most Windows development tools and productivity software (such as Microsoft Excel). Even the Web browser problem becomes much simpler, since we can use Active Server Pages to directly access the application's business objects to create HTML pages for the browser:

At this point, it may not be entirely clear how this architecture can be implemented with Visual Basic. As we get further into business objects, we'll delve into the details and see just how well this whole idea works out. This architecture has been used in the development of a number of large projects with great success - that's happy users, good performance and long term maintainability!

UI-centric Business Objects

Instead of putting the bulk of the code for our application behind Visual Basic forms, we'll move that code into business objects. By doing this, we can apply object-oriented design techniques to build an object model, and put the business rules and processing into the objects of that model.

In Chapter 1, we defined what a business object is - on a fairly high level. In summary:

Business objects are the core of your business solution. With Visual Basic, they are instances of class modules that are designed to represent the real-world 'objects' with which our application is dealing.

In Chapter 3, we'll go through some techniques that we can use to identify real-world objects and translate them into software objects.

The user-interface is really a client, or user, of the business objects. While the user-interface displays and retrieves data from the user, the business objects need to provide all the business functionality and capabilities required by the user interface. At the same time, the business objects need to enforce all the business rules and relationships to meet the design requirements of our application.

The business objects should provide appropriate access to all the functionality of the application. The presentation tier, even including macros in other applications such as Microsoft Word, should be able to use the components containing our business objects to do their work.

The business objects contain the business rules for the real-world entities that they represent. For instance, if a business rule says that a customer must have a last name, there should be code in the customer business object to make sure that the last name isn't blank. You might find something like the following code in the LastName property of a customer object:

Public Property Let LastName(Name As String)
  If Len(Name) = 0 Then
    Err.Raise vbObjectError + 10, "Customer class", _
      "Last name can not be blank"
  Else
    strLastName = Name
  End If
End Property

Business objects interact with each other by following the rules laid out by our object model. Business rules dictate how the objects work with, or relate to, each other.

As an example, perhaps a customer can have many phone numbers. Translating this into business objects, our customer object will have many phone objects. There is a relationship between the customer object and each phone object, in that each phone object is owned by a customer. This relationship needs to be coded somehow into either the customer or the phone objects. For instance, the phone objects may be stored as a collection inside the customer object:

Option Explicit

Private PhoneNumbers As New Collection

Our UI-centric business objects should be designed to accurately reflect the real-world business entities that our application needs to model. This means that each object should represent some entity, along with any attributes, methods and business rules that affect that entity. Our overall object model should also enforce any business rules regarding how objects interact.

Any user-interface will interact exclusively with our UI-centric business objects, and so these objects must not only implement and enforce all our business rules, but they need to provide all the capabilities that the UI developer will require to create a rich, interactive interface for the user.

Data-centric Business Objects

By splitting the data-centric processing out of our business objects, we've provided ourselves with a great deal of flexibility in terms of how we deploy our application. We can choose to implement the data-centric business objects in a DLL, so that it runs on the client workstation, or we can put them in an EXE running on another machine on the network. We can easily compile them into a DLL and run them within Microsoft Transaction Server (as we'll see later in the book).

Our data-centric business objects typically provide certain services that are used by our UI-centric business objects. When a UI-centric business object needs some work done that it can't do by itself, it just has a data-centric object do the work.

Our UI-centric business objects should provide the UI developer with an intuitive and reliable model of the real-world objects in our business. Our data-centric objects need to provide the UI-centric objects with a robust and powerful mechanism by which data can be saved and retrieved as needed. This means that all interaction with data sources should be managed by our data-centric objects; the client-side business objects should have no idea how data is stored or retrieved.

ODBC Data Sources

Most applications deal with data, and most data is stored in some type of database that we can reach using ODBC (Open Database Connectivity). Because of this, most of our data-centric business objects will make heavy use of data access technologies such as DAO, RDO and ADO.

Since our server-side business objects will handle all the database interaction, this is where we'll find all the SQL statements, recordsets, resultsets, database objects and so forth. It's the job of the data-centric business objects to translate all these relational database concepts into data that is useful to our client-side business objects.

As an added bonus, our data-centric business objects can be used to shield much of our application from the differences across various SQL databases. Since all the database interaction is managed by our server-side objects, we have only one place to change code when we need to support a new database.

While it's true that we can use ODBC to help minimise the differences between various databases, there is a cost. A prime example is an application that needs to run on a single workstation with an Access database and in a WAN environment with an Oracle database. Using ODBC, we can write a single set of data-centric objects that work with both databases. Unfortunately, our code will be optimised for neither Access nor Oracle, very possibly leading to unacceptable performance.

With little or no impact to our user-interface or the UI-centric business objects, we can make our data-centric business objects smart enough to deal with both types of database. These objects can be written to detect the type of database and to run the appropriate queries and use the appropriate data access techniques for either Access or Oracle as needed.

Along the same line, if we need to migrate from one kind of database to another then we only need to change the server-side business objects, not all the business objects on the clients. Perhaps our database was a legacy system, and we used screen-scraping technology to get at the data. If we later convert to Microsoft SQL Server, we can just change the data-centric business objects to get the data from the new source - with no impact to our user-interface or the client-side business objects.

Non-ODBC Data Sources

It's important to remember that data isn't always stored in a relational database. Many applications need access to specialised hardware or software. For instance, we may have objects that represent information that is stored in a proprietary laboratory information management system (LIMS) or some equivalent type of system. Perhaps our objects represent physical devices that load trucks with an appropriate mix of cement, sand and gravel to make ready-mix concrete.

As we discuss 'data-centric' business objects, we need to keep in mind that we aren't always working with ODBC, SQL, and other relatively simple data sources. Fortunately, the CSL architecture we're talking about allows us to shield our client-side business objects from all these details through the use of server-side objects. Our data-centric objects can interact with whatever data source we need to deal with, whether complex or not. At the same time, our UI-centric business objects can provide an easy, consistent interface for use by the UI developers.

Minimise Licensing Costs

If we're implementing our architecture across multiple physical machines, we have the option of moving the data-centric objects off the client workstations and on to a central application server machine. We might do this by putting the objects in an ActiveX EXE server and using DCOM, or we might put them into an ActiveX DLL server running within Microsoft Transaction Server. We'll look at examples of each approach later in the book.

By centralizing the data-centric objects on a separate application server machine, we've also centralised all the database interaction. This means that the clients don't need to have any direct access to the database, only to the application server.

This has a couple of benefits. Logistically, we may have simplified the client desktop - since we don't need to install and maintain ODBC drivers and data connections out there. Also, suppose we have 100 client workstations: traditionally, we'd have to buy 100 client licenses for our database. By using a centralised set of data services, we can let the clients share the connections to the database, thus reducing the number of client licenses that we need to purchase. Since most database vendors charge licensing on a per connection basis, we can save on licensing costs.

Data Services Tier

The final area we need to discuss is the data services tier. At a minimum, this tier is responsible for creating, updating, retrieving and deleting data from a data source. In many cases, it also includes more complex features, such as enforcement of relational integrity and other data-related rules.

In most cases, this data handling will actually take place on a database server. This allows us to put quite a bit of processing on the database server, using SQL-stored procedures and triggers. This can often provide important performance benefits, since most SQL database servers can optimise stored procedures so they run very efficiently.

It's important to note, however, that much of the work that a database server might have done in a application may actually be more appropriately placed in our data-centric business objects rather than in SQL code. SQL is notoriously difficult to work with. It doesn't compare to tools such as Visual Basic for features such as debugging and flexibility. While it's still very valid to do some SQL coding in an application, it is often much more cost effective to move the processing to a different layer of the application - where we can bring more powerful development tools to bear on the problem.