Qualities of a Good Middle-Tier Architectureby Satya Komatineni
IT programmers and consultants spend a lot of time designing middle-tier architectures. This practice is common in typed languages like C++, Java, and C#. This article examines the qualities of a good middle-tier architecture so that an assessment can be drawn of these architectures. In conclusion, the more popular middle-tier solutions, namely stored procedures, EJBs, COM+, and SQLJ are examined for their pros and cons.
I have been consulting with various IT organizations since 1987, focusing on C++, Java, and C#. An important observation during this span of time is that IT programmers spend a lot of time designing the middle tier. During the client/server days, this used to be the data tier. The web paradigm pushed the middle tier to prominence. In addition, media and educational institutions have also propagated the following ideas:
- Multiple tiers are better than a single tier.
- Business logic is better represented in objects.
- One must work only with objects and not data.
The presupposition is that it is somewhat inferior not to have such theoretical foundations to any "enterprise-worthy" architecture. It is only with trepidation that one could meekly suggest stored procedures as an effective model (not necessarily the ultimate physical implementation) for the middle-tier architecture.
Recently I was talking with a colleague of mine, Richard Czyzewski, on this topic of middleware. We have talked about CORBA, object databases, RMI, EJBs, web services, and COM+. I was routinely mentioning to him how these systems have become increasingly easier to work with, but still lack some very convenient and practical features of stored procedures. This has led me to analyze stored procedures in the hope of arriving at a good set of middle-tier architectural qualities. This article is a summary of and commentary on these qualities. I assume a relational back end for much of this article.
What is Middle-Tier Architecture?
During the client/server days, it was common for a client GUI program to talk directly to a database server. Web applications introduced the three-tier model by default: the browser is the client tier, the database the back-end tier, and the web server and its extensions became the middle tier.
This middle tier offered better scalability options, in terms of connection pooling and stateless high transactional volumes. The architecture that goes into this tier is usually called the middle-tier architecture. The software solutions in the middle tier are called the middleware. There are a lot of good examples of middleware: ColdFusion, PHP, J2EE, .NET, and so on and so forth.
When a typed language like Java or C# is chosen to implement this middle tier, developers tend to create fairly extensive architectures to support their applications.
What Happens in the Middle Tier (Responsibilities)?
The two primary activities in a middle tier are reading and writing data. Reading data involves retrieving the raw data based on input parameters, massaging the data, and delivering the data in the requested format. Writing data involves manipulating the data stored, using certain business rules and processes. In a sense, the write operation changes the state of the database server.
How Do Middle Tier and Business Components Relate to Each Other?
If you examine the functionality of the middle tier, it doesn't take long before you realize that the middle tier work can be accomplished by business components. So in a sense, the architecture of the middle tier becomes the architecture of components.
Business components allow you to write business logic using a set of interfaces and replace functionality with newer versions. Multiple vendors can provide implementations of a business component that you can make use of. The standard middle-tier architectures such as EJB, COM+, and CORBA are essentially business component models.
The lineage of business components lies in objects. Their primary model of inheritance, polymorphism and encapsulation, is rooted in having a physical "object." Relational back ends (the independent beasts that seem to have resisted all object winds very successfully) don't want to be tied down by objects. Without that explicit tie-in with data, business components seem to expose their drawbacks when we consider them for the middle tier. Business components (or objects) are not as malleable, dynamic, or fluid as one might wish them to be in a changing environment like relational databases that serve multiple needs.
The real nature of middle tier architecture, particularly when back-ended by relational databases, seems to be a collection of tasks or services that can be composed at will into newer services. This leads us to service-oriented architectures (SOA). I prefer to call them "task-oriented architectures," because "service" seems to connote a tier with corresponding transport liabilities. On the other hand, a "task" is an in-process, stateless architectural element that can be executed in a local process or a remote process.
How Can the Middle Tier Exist in Single-Tier Applications?
When middle-tier architectures are designed, little thought is given to single-process constraints. For example, say I write two business components as session beans in an EJB tier. As my inverse luck would have it, I will have a need to run the same business components in a command-line application that I am tasked to finish in 10 minutes. I should be able to do this without having to go through all of the EJB stuff in those 10 minutes. So, in any architecture that we choose, we should be able to invoke tasks in both single-tier and multi-tier applications. As a result, task-oriented architecture is not only a concern of the middle tier, but also of every application, both standalone and distributed.
Ideas on Tierless Computing
The above observations lead me to predict that the future of EJBs and COM+ lies in "tierless computing." In my mind, they are heading there slowly; I believe COM+ is very close to being there. Let me elaborate: tierless computing states that a business component or a task can run independent of the logical tier in which it is embedded. In a sense, every tier becomes a container for this task to run in. In this space, a task (usually implemented as an object with a well known method) becomes the primary unit that moves along, gets replaced, has security applied to it, etc. This idea also resonates with REST. REST (Representationl State Transfer) is a basic http based approach to web services with out the frills of SOAP. Although there are some guidelines for REST, essentially any web site that is capable of replying to an incoming http request with an XML stream back can be said to be following the REST based approach to service oriented architecture.
Qualities of a Good Middle-Tier Architecture
Back to the central topic of this article, namely the qualities of a good middle-tier architecture. As I go through the derivation of these qualities, I use stored procedures as examples to discuss these qualities. This does not mean that stored procedures are perfect for all scenarios; stored procedures happen to draw on some very good qualities of middleware.
Easy to Discover
I call this principle the Genie principle or the Genie Rule. As they say, when you find a Genie, perhaps your first wish should be to speak the Genie's language. An important requirement for a middle tier is that it is reusable. More than one developer will make use of the assets created as a middle tier. If you are encouraging other developers to use what you have created, the first fundamental requirement is that they can find what you have created. So when a new developer comes on board, he/she should be able to browse through what's out there for him/her to use.
You would think such a requirement is obvious. Instead, a more likely scenario is this: a developer is pointed to a source-code control system with a certain project name, containing the source code and libraries for the middle tier. The developer will be lucky to get this project downloaded and built; he or she is not sure what other dependencies exist for the middle tier to even compile. Even after compilation, the developer is faced with an array of Java classes and doesn't even know where to start. For example, there may be 100 classes in that package; there may be only four interface classes that the developer wants you to use. But this is usually not obvious.
The developer would go through a different experience if the middle tier was available as stored procedures. You can use any database browser to list the available stored procedures. Stored procedures tend to be fairly standalone with very little coupling to other procedures. In some database systems, these procedures are even arranged into packages for better classification.
The designers of middleware should give a lot of thought to how developers will discover the middleware's functionality.
Easy to Call
Let us examine the story above a bit further. Once you know that there is a procedure or function that you can call, the next questions is: what do you have to do to invoke the procedure with some sample data? With the traditional Java environment, you have to create and compile the client code before you can call the procedure. With stored procedures, it is a relatively easy task (this may not be true with Oracle cursors). In SQL server, just type in the procedure name and the arguments and hit the execute button. You will see the results right there in a certain default format (rows and columns).
Easy to Call from Multiple Environments
Another important characteristic of stored procedures is that you can virtually call them from any environment: JDBC, ODBC, C API, Perl, etc. This may not seem that important at first, but in the last 10 years, I have noticed that an IT company is more likely to change their front-end technology or language, compared to their database. So a middle-tier architecture that is accessible to more than one technology is quite valuable. For example, stored procedures can be called from your reporting engines, such as Crystal Reports and Actuate, while you develop the primary system in Java. This cross-compatibility is what made relational databases one of the mainstays of IT environments.
Easy to Monitor the Response
Sometimes the Web is described as a large database of documents that changes its state with each
POST. At least, that's what happens in a database all the time.
The middle tier is expected to effect these changes. In other words, when you call an API in the
middle tier it would have altered the system state in some manner. The question to the caller is, "Did the intended change take place?" How can one verify this? In the case of a
stored procedure, you can just take a look at the affected tables and see that the new rows are there, or existing rows have changed, etc. This traceability of response is important for a quick testing cycle. On the other hand, if we take a black-box approach, you are never really certain if the changes took place or not. In tactile systems, this is called tangible feedback effect. In your car, if you press on the gas pedal, it gives you a certain feedback as to whether it is working or not, even without looking at the dials.
Easy to Create a New Component
The above four qualities ensure that you can discover, test, evaluate, and call middle tier APIs. Assume that in the process you have decided that the existing functions will not do. So you want to write a new component or function. In stored procedures, simply start typing in your new code and the database container will absorb it instantaneously. The build/creation environment is very easy.
Compare this to writing a new session bean. Perhaps the IDEs have realized this challenge and provided a one-button approach to creating a session bean. Even with that, you need to know what JDBC is, and probably a host of other APIs, depending on the functionality.
Easy to Test the New Component
After creating a new component, one has to write a test harness to explicitly test the component. You can't simply invoke the API declaratively. For example, you can do this in stored procedures, as mentioned above. You can also do this for some implementations of web services where you can type in the arguments and execute the service. This is not impossible to design into all sorts of systems, but first it should be recognized as a central quality attribute of the middle tier.
Easy to Deploy the New Component
Now that we have the component and have tested it, we are now eager to deploy it. Again, with stored procedures this deployment is instantaneous. As soon as you compile the stored procedure in the database it is immediately available for usage. The Java components are gradually getting there; still, this process is not as transparent. The idea is to upgrade/grow functionality one class at a time. In addition to stored procedures, JSPs are a good example of this model. Again, this goes to show that you can always implement good qualities as long as we can recognize them as important.
Easy to Deploy the Multiple Versions of a Component
Now we are getting into an area where stored procedures don't do that well. Take the case of backward compatibility of deployed components. Stored procedures bind directly to a physical name. There is no separation of interface and implementation. This bites when it comes to backward compatibility. With objects, you can have multiple versions of the implementation available so that older clients can continue to bind to older implementations, while newer ones will bind to newer implementations.
Easy to Categorize Components
This is a quality that stored procedures are really crying out for. Here is the problem: you go to a database navigator and list the stored procedures. You will see hundreds of them listed, one after the other. It is very difficult to find out which one you want from this stack. Oracle helps this process by allowing packages -- collections of stored procedures that are grouped together into modules.
Considering that these stored procedures are independent entities, there is no reason why one should not be able to categorize them into any sort of hierarchy. This means that a procedure can be part of two or three packages, if that makes sense. This organization should be possible even dynamically.
The same classification approach can be taken to services. This is what is possible with service directories, such as UDDI. But there is no reason why this classification mechanism needs to be as complicated.
For example, the URIs in a REST-like framework should be able to effect this classification easily.
Easy to Document a Component
Because the middle tier (by its very nature) is coded for other people to understand and use, how this facility is documented becomes important. Something like JavaDoc that can drill down through packages and classes and functions is a good start.
Stored procedures, for example, suffer from a lack of emphasis on documentation. Because stored procedures are declarative, the code can be cryptic, and documentation becomes crucial to see the intent of the author. But at the same time, it should be pointed out that normally, stored procedures (despite their joins) can be read easily by an expert programmer in a single sitting. So it is a mixed bag with stored procedures.
I think the JavaDoc approach can be extended, whereby programmers and users can add to the documentation in an ad hoc manner dynamically any time while the program is running. Essentially, you have a documentation server for the APIs. This is similar to blogs where the API becomes the topic of a blog. This documentation can be added as an afterthought both by the author and also by the users. As time goes on, this accumulated documentation will be an excellent guide to the users.
Pages: 1, 2