DBMS, February 1998
DBMS Online: Component Assembler. By Tom Spitzer

Tiers Without Tears

Packaging logic into components makes multitier architectures less painful.


My colleagues and I have written a great deal about three-tier architectures. Itıs a concept that makes a lot of sense, but can it be implemented by mere mortals? I must admit some insecurity here, because Iım 42 years old, and Iıve yet to develop a real three- (or more) tier application on my own. So far, old fashioned two-tier applications have served me pretty well; where they have not, Iıve availed myself of team development or tools that essentially generate a three-tier solution. I realize that I need to grow up, take the cure, and walk the walk. I also think that what Iım talking about would be a great deal more clear if I could come up with a good example; it would be even better with a library of real-world examples.

Most of the applications that I have developed have consisted of one or more data entry forms, each of which has logic associated with it. For the last few years, I used a development tool with a screen builder that allowed me to drop controls into the form. Many of the controls had logic associated with them, and there were three times when the logic had to be evaluated: when the form was presented, to determine if default values or value lists depended on business rules; when the operator exited the control, to validate the entry; and when the operator indicated that she was done, to validate the form. Most of the development environments I have used allowed me to enter code into functions associated with the target control (and still do). Visual Basic is a prime example. As a result, I could code all my application logic right into the form definition.

It did not take too long to figure out that I would pay a price for taking this convenient approach. Most notably, my application could not reuse any of the rules that were being applied within the form definition. So I wrote libraries of functions and built those function libraries into each of the applications that needed to use them. Instead of coding complete functions into the form definition, I called the functions with parameters that were needed to associate the specific data from the form with the generic business rule-processing function. This promoted reusability, but it did not allow me to deploy the forms portion of the application apart from the function library. What I needed was a way to write and deploy the code that evaluates business rules so that the code can run as a separate process from the one in which the forms execute.

We all know that component architectures allow us to build logic components and run them out of the process of our forms package and that the advantage of using a standard component platform (for example, COM, CORBA, or JavaBeans) is that it manages all the plumbing for you. As a long-time developer of 4GL Windows applications, I have a lot of experience with the COM architecture, which is the context in which I will continue to describe the construction and deployment of a middle-tier layer.

The Problem

I happen to have a simple problem that I need to solve in a way that ensures scalability to large numbers of simultaneous users around the world. Itıs a basic service enrollment application, with a variety of ways for people to enter and submit their enrollment information. Initially, some of the enrollees will enter their information at my Web site, and others will deliver it to me in an Access database table. This enrollment information needs to be added to a database, a unique ID has to be generated and delivered back to the enrollee, and a verification process has to be initiated. By developing the logic in middle-tier components, I should be able to use it in each of my deployment scenarios.

The business logic includes:

I need to implement this logic so that it can be executed from a Web page, a standalone application, or a message carrying the request. We want the customers to be able to enroll from their Web browsers, and we want customer service agents to take enrollment information over the phone as well. To give the agents a very "Windows 95" user interface, we may build a version of the application in Visual Basic for them, but it needs to use the same logic as the Web version, so it should use the same components. Developing and deploying three-tier applications pose interrelated challenges. Iıll focus on design aspects to start with, and move into deployment issues later.

Some Solutions

For illustration purposes, letıs look at some common application tasks and how we can address them. First, when the form loads, we want to populate the Hub programs combo box with the names of EDI Hub companies with whom we are sponsoring programs. Second, we want to validate that the ZIP code a user has entered is valid for the state and city entered. Finally, we want to add the customer to a table of pending enrollments.

To set this up, letıs call my form ECAddCust and say that I have put a combo box named cboEDIHubs on it. In the old days I might have called a local function from my form load event to populate an array, and then I would have loaded the array into the cboEDIHubs combo box. That simple function call would have looked like this:

aHubs =  RetrieveHubs()

Migrating this simple function into a COM object (which I wrote in Visual Basic), I need to perform a couple extra steps before I can call the function. First, I have to initialize an object variable and assign it the type of middle-tier logic server, which in this case is the object named ECEnrollMgr. Second, I have to create an object of the specified type. After that I can call the function. It looks like this:

' Create an object variable of type ECEnrollMgr.
clsEDIHubs
Dim oHubs As New ECEnrollMgr.clsEDIHubs

' Create an instance of the object and reference it with the oHubs variable 
Set oHubs = CreateObject("ECEnrollMgr.clsEDIHubs")

' Call the RetrieveHubs function to populate the local array
aHubs = oHubs.RetrieveHubs()

Validating the city, state, and ZIP combination works in a very similar fashion. The "two-tier" code is

' Call the local ValidateZipDAO function to test the City/State/ZIP combination
' This function runs against an Access table, using DAO for data connectivity 
If Not ZipValidDAO (txtCity.Text, txtState.Text, txtZip.Text)  Then

   ' if the validation failed, display appropriate message 
    MsgBox "Invalid City, State, ZIP Code combination"

End If
The three-tier code is:
           
' Create an object variable of type ECEnrollMgr.
Validator
Dim oValidator As New ECEnrollMgr.Validator
  
' Create an instance of the object and reference it with the oValidator variable 
Set oValidator = CreateObject("ECEnrollMgr.
Validator")

' Call the ValidateZipDAO function to test the City/State/ZIP combination
If Not oValidator.ValidateZipDAO _
   (txtCity.Text, txtState.Text, txtZip.Text) Then

   ' if the validation failed, display appropriate message
    MsgBox "Invalid City, State, ZIP Code combination"
End If

Notice that in both versions I passed the values of the text boxes to the validation function as parameters. In the two-tier version, doing so reduced the coupling between the validation function and the form and facilitated my migrating the function into an independent object. I can run this as a distributed application, which essentially means that the ECEnrollMgr component resides on another machine on my network besides the one on which the data entry form is running. When I do that, DCOM does all the work of running the object on the remote computer, sending the parameters over the network to the object that needs them and returning the result (in this case, true or false) back to the client that requested the method invocation.

Adding a new customer is a fairly straightforward exercise as well. I simply create a customer object, which has a number of properties representing customer attributes, like company name, contact person, address, and so on. In my client code, I assign the user-entered values from the form to the properties of the instantiated ECCustomer object. These property assignments resolve to method calls within the ECCustomer object; whatıs really happening is that each assignment calls a method within the object that treats the user-supplied value as a parameter and assigns it to the property. Finally, I call the Add method on the ECCustomer object:

' Declare and instantiate object variable of type ECEnrollMgr.ECCustomer
Dim oCustomer As New ECEnrollMgr.ECCustomer, lUpdate As Boolean
Set oCustomer = CreateObject("ECEnrollMgr.ECCustomer")

' Assign entered values from controls to property values
oCustomer.Company_name = txtCompany.Text
oCustomer.Contact_name = txtContact_name.Text
oCustomer.Contact_title = txtContact_Title.Text
ı
' Call add method that adds the customer to the table
lUpdate = oCustomer.Add()

The Add method inserts a row into the customer table, using the values of the customer property variables to update the corresponding columns in the customer table.

You can use the customer object the same way in a Web application with Internet Information Server (IIS) and Active Server Pages (ASP). The only difference is that user-supplied data is delivered from controls on the Web page rather than native Visual Basic controls. The code looks like this:

' Instantiate the customer object in the server context
Set oCustomer = Server.CreateObject("ECEnrollMgr.
ECCustomer")
' Assign values from submitted HTML form to customer properties
oCustomer.Company_name = Request.Form("Company")
oCustomer.Contact_name = Request.Form("Contact_name")
oCustomer.Contact_title = Request.Form("Contact_title")

' Call add method on customer object
lUpdate = oCustomer.Add()

Again, the object can execute on an application server separate from the Web server, in which case DCOM does the work of packaging and communicating the parameters from the ASP code to the instance of the object which is servicing the request.

Benefits and Challenges

Letıs recap the practical benefits of taking a three-tier approach and the challenges we need to address before we can make our application work. This development model gives me the highest degree of reusability I, as a developer of primarily 4GL applications, have ever had. The class objects I create and deploy as components are available to any OLE-enabled development tool, including the VBA scripting languages within the Microsoft Office products and the scripting capabilities of Internet Explorer and Internet Information Serverıs ASP.

The second group of practical benefits I get comes from removing all database access from the client application. This allows me to develop and test the client application against a test database and then switch the database connection to a production database without affecting the user-interface tier at all. It also makes the fact that the application is reading and writing to multiple databases very transparent to the user-interface tier, which is only interacting with objects. An associated benefit should be a reduction in the number of open connections to the database; the challenge becomes designing a system architecture that actually lets you realize this benefit.

The question of database access raises most of the challenges in this model; of course, nothing useful comes for free. I have not eliminated dependence on data sources, Iıve only moved the dependence away from the client application into the middle tier. Iıve also failed to insulate the client completely from changes in the data structure. If I realize that I need to add a column to a table, I will need to modify the data entry form to capture a value for the new column. If I wanted to address this, I would need to maintain a database to record the assignment of columns to entry forms and use it to drive runtime generation of the forms. In fact, thatıs how my companyıs application software already works.

The most telling challenge is how to manage concurrency and loading of the application. If I simply put my components on a server and call them via DCOM, I am open to the very same data resource contention issues that arise in a two-tier application. Contention issues are in fact exacerbated because fine-grained objects cannot, and should not, be aware of resource dependencies. In my old two-tier application, the application knew what resources it needed, so I programmed in tests for their availability and reserved tables and rows that I knew I was about to update. While Iım happy no longer to have to do this work, I do feel some loss of control in relying completely on the isolation management of the underlying database API, and I recognize that "something" needs to do the coordination that I used to do manually!

Loading and scalability are my other big concerns. If there are numerous users, each user will instantiate another instance of the objects, and the number of processes running on my server will get very large. We application developers risk straying into trouble if we try to do the systems programming necessary to address these challenges. Fortunately, there are off-the-shelf solutions ı namely transaction processing monitor and object request broker products ı that are becoming increasingly accessible to folks like us. I think of the new breed of these products, characterized by Sybaseıs Jaguar CTS and Microsoftıs Transaction Server (MTS), as "component coordinators" because they provide a runtime environment in which they coordinate the execution of your middle-tier components and manage the transactions on which the components are working.

What Transaction Server Brings to the Table

You can develop three-tier applications without a component coordinator, but you would be foolish to deploy without one. In a Microsoft-centric development environment, itıs easy to choose MTS as your component coordinator, which Microsoft is integrating increasingly tightly with Windows NT and IIS. Microsoft is heavily promoting the free download evaluation copy of the product, but itıs important to realize that MTS will cost about $2,000 per server when you get around to production deployment. MTS 2.0, which is currently in beta 3 and will ship with IIS 4.0 in early 1998, does much more than manage transactions, and it does that admirably. You can include multiple components in a transaction, each interacting with a separate database, and MTS will either commit or roll back the entire transaction. But, you would expect that from a product that calls itself a transaction server.

What you might not expect is object pooling. With object pooling, the components execute in the MTS process space, and MTS assigns running objects to clients when it receives a client request; for instance, the client invokes one of the methods, like my ValidateZipDAO method. MTS will manage a pool of ECEnroll objects, and will dynamically assign one of them to the client invoking the method. This reduces the number of objects required to service multiple clients, and it eliminates the overhead of having to instantiate a new instance of a component every time a client connects. It also implies that the client cannot rely on getting the same instance of an object each time it invokes one of that objectıs methods. As an application designer, you cannot rely on the object to "remember" anything about the client from one invocation of a method to another. Of course, this has serious consequences for how you design your components.

Design Considerations

Todayıs mantra says develop fine-grained, stateless components. Fine-grained components tend to be easier to develop and test because their purpose is tightly focused. At runtime, they are better behaved because they require fewer system resources and can be activated and deactivated quickly and as needed. For many of us though, using fine-grained components is going to be a balancing act. At the limit, you could package each method into a separate component. I think that this is a little extreme. Taking this approach would make developing and deploying applications fairly cumbersome. The middle ground, which makes sense, would be to package each class into a separate component. To get to this middle ground, I need, at the very least, to extract my clsEDIHubs, Validator, and ECCustomer classes and build them into separate components. To be dispassionate about it, I should split my Validator class into several components that include validation functions that will be executed together. I should split ECCustomer into a retrieve customer information component and an update customer component.

Statelessness is a slightly more difficult concept to grasp and will probably be the biggest challenge for application developers migrating to three-tier application development using a component coordinator. Statelessness characterizes components that donıt retain property values between method invocations. In a component coordinator environment, where the client application does not know which instance of a middle-tier component will be responding to its next service request, the client application cannot rely on the middle tier component holding information maintained in the client. The component coordinator can release the component following the completion of a method call, making its resources available to other processes.

The way Iıve coded my add-customer component is "somewhat" stateful, since the client copied its state to the object prior to invoking the Add method. There are several approaches to making the component more stateless. The most obvious solution would be to pass the values of all the columns as parameters. I dislike this method because it can result in having to pass a large number of values as parameters. I should probably get over my resistance to this technique because I have to pass each of the values explicitly and separately no matter how I do it. Another approach that appeals to me involves splitting the component into two components. A stateful nontransactional object (for example, CustomerInfo) would collect the data values associated with each customer. This object would call a stateless transactional component, passing its properties as para-meters, either explicitly or in an array. These two objects would be tightly coupled, the client would be somewhat "cleaner" and the components would be more fine grained, enhancing the scalability of my solution.

Throughout this discussion, Iıve tried to remain at a fairly conceptual level. I admit that itıs very difficult to stay at a management level and avoid straying into myriad technical details. There are more articles to be written about the details of component design and deployment and about the particulars of using component coordinators like Microsoft Transaction Server. I am looking forward to migrating to IIS 4.0 and MTS 2.0 myself, and Iıve been extremely frustrated by my inability to install the beta 3 version of IIS 4.0 successfully when I was working on this article. It looks like a reinstallation of Windows NT lies ahead. I should be up and running again by the time I write the next chapter in this story.


Vendor Information

Tom Spitzer is vice president of product technologies at The EC Company, a Silicon Valley startup entering the electronic commerce marketplace. You can email Tom at tspitzer@eccompany.com.

What did you think of this article? Send a letter to the editor.


Subscribe to DBMS -- It's free for qualified readers in the United States
February 1998 Table of Contents | Other Contents | Article Index | Search | Site Index | Home

DBMS (http://www.dbmsmag.com)
Copyright © 1998 Miller Freeman, Inc. ALL RIGHTS RESERVED
Redistribution without permission is prohibited.
Please send questions or comments to dbms@mfi.com
Updated January 12, 1998