This article provides a high-level introduction to J2EE. It is taken from chapter one of the book Beginning J2EE 1.4 From Novice to Professional, written by James L. Weaver, Kevin Mukhar, and Jim Crume (Apress, 2004; ISBN: 1590593413).
J2EE Essentials - Multi-Tier Architecture (Page 2 of 7 )
One of the recurring themes that you’ll run into with J2EE is the notion of supporting applications that are partitioned into several levels, or tiers. That is an architectural cornerstone of J2EE and merits a little explanation. If you are already familiar with n-tier application architectures, feel free to skip ahead. Otherwise, the overview presented here will be a good introduction or review that will help lay the foundation for understanding the rationale behind much of J2EE’s design and the services it provides.
One of the recurring themes that you’ll run into with J2EE is the notion of supporting applications that are partitioned into several levels, or . That is an architectural cornerstone of J2EE and merits a little explanation. If you are already familiar with n-tier application architectures, feel free to skip ahead. Otherwise, the overview presented here will be a good introduction or review that will help lay the foundation for understanding the rationale behind much of J2EE’s design and the services it provides.
If you think about what a software application is composed of, you can break it down into three fundamental concerns, or logical layers. The first area of concern is displaying stuff to the user and collecting data from the user. That user interface layer is often called the presentationlayer, since its job is to present stuff to the user and provide a means for the user to present stuff to the software system. The presentation layer includes the part of the software that creates and controls the user interface and validates the user’s actions.
Underlying the presentation layer is the logic that makes the application work, and handles the important processing. The logic in a payroll application that, for example, multiplies the hours worked by the salary to determine how much to pay someone, is one example of this kind of logic. This logical layer is called the business ruleslayer, or more informally the middle tier.
All non-trivial business applications need to read and store data, and the part of the software that is responsible for reading and writing data—from whatever source that might be—forms the data access layer.
Simple software applications are written to run on a single computer. All of the services provided by the application—the user interface, the persistent data access, and the logic that processes data that’s input by the user and read from storage—all exist on the same physical machine and are often lumped together into the application. That monolithic architecture is called “single tier” because all of the logical application services—the presentation, the business rules, and the data access layers—exist in a single computing layer.
More significant applications may take advantage of a database server and access persistent data by sending SQL commands to a database server to save and retrieve data. In this case, the database runs as a separate process from the application, or even on a different machine than the machine that runs the rest of the program. The components for data access are segregated from the rest of the application logic. The rationale for this approach is to centralize data to allow multiple users to simultaneously work with a common database, and to provide the ability for a central database server to share some of the load associated with running the application. This architecture is usually referred to as “client-server.”
It’s convenient and more meaningful to conceptualize the division of the responsibility into layers, or tiers. This software architecture can be shown in two tiers:
One of the disadvantages of two-tier architecture is the fact that the logic that manipulates the data and applies specific application rules concerning the data is lumped into the application itself. This poses a problem when multiple applications are needed to use a shared database. Consider, for example, a database that contains customer information that is used for order fulfillment, invoicing, promotions, and general customer resource management. Each one of those applications would have to be built with all of the logic and rules to manipulate and access customer data. For example, there might be a standard policy within a company that any customer whose account is more than 90 days overdue will be subject to a credit hold. It seems simple enough to build that rule into every application that’s accessing customer data, but when the policy changes to reflect a credit hold at 60 days, updating each application becomes a real mess.
You might be tempted to try to solve this problem by building a reusable library that encapsulates the business rules, and when the rules change just replace that library, rebuild the application, and redistribute it to the computers running the application. There are some fundamental problems with that strategy though. First, that strategy assumes that all of the applications have been created using the same programming language, run on the same platform, or at least have some strategy for gluing the library to the application. Next, the applications may have to be recompiled or reassembled with the new library. Moreover, even if the library is a drop-in replacement without recompiling, it’s still going to be a royal pain to make sure that each installation of the application has the right library installed simultaneously (it wouldn’t do to have conflicting business rules being enforced by different applications at the same time).
In order to get out of that mess, the logical thing to do is to physically separate those business rules out from the computers running the applications onto a separate server so that the software that runs the business rules only needs to be updated once, not for each computer that runs the application. This adds a third tier to our two-tier client-server model:
In this model, all of the business logic is extracted out of the application running at the desktop. The application at the desktop is responsible for presenting the user interface to the end user, and for communicating to the business logic tier. It is no longer responsible for enforcing business rules or accessing databases—its job is solely as the presentation layer. Bear in mind that at this point we’re talking somewhat abstractly and theoretically. In a perfect world without performance and other implications, the division of responsibility would be very clear-cut. You’ll see throughout this book that you will make practical, balanced implementation decisions about how responsibilities are partitioned in order to create an application that is flexible and performs well.
Typically, in a deployed application, the business logic tier executes on a server apart from the workstation (you’ll see shortly that this isn’t absolutely required, though). The business logic tier provides the logical glue to bind the presentation to the database. Since it’s running on a server, it’s accessible to any number of users on the network running applications that take advantage of its business rules. As the number of users demanding those services increases, and the business logic becomes increasingly complex and processor-intensive, the server can be scaled up, or additional servers added. Scaling a single server is a lot easier—and cheaper—than upgrading everyone’s workstations.
One of the really great things that this architecture makes possible is the ability to start to build application models where the classes defined in the business logic tier are taken directly from the application domain. The code in the business logic layer can work with classes that model things in the real world (like Customers) rather than working with complex SQL statements. By pushing implementation details into the appropriate layer, and designing applications that work with classes modeled from the real world, applications become much easier to understand and extend.
It’s possible to continue the process of partitioning the application functionality into increasingly thin functional layers. You’d reach a point of diminishing returns fairly quickly, since the performance penalty for the network communication between the layers would start to chew up any gains in performance. There are some very effective application architectures based on “n-tier” architecture—the application architect is free to partition the application into as many layers as appropriate—based on the capabilities of the computing and network hardware that the system will be deployed on.
The J2EE architecture is based on the notion of n-tier applications. J2EE makes it very easy to build industrial-strength applications based on 2, 3, or n application layers, and provides all of the plumbing and wiring to make that possible.
I should mention that n-tier architecture does not demand that each of the application layers run on separate machines. It’s certainly possible to write n-tier applications that execute on a stand-alone machine, as you’ll see. The merit of the application design, however, is that the layers can be split apart and deployed on separate machines, as the application requires.
Labeling a particular architecture as “three-tier,” “five-tier,” etc. is almost guaranteed to spur some academic debate. Some insist that tiers are defined by the physical partitioning, so if the application components live on client workstations, an application server and a database server machine, it’s definitively a three-tier application. Others will classify applications by the logical partitioning where the potential exists for physical partitioning. For the discussions in this chapter, I’ll take the latter approach with apologies in advance for those who subscribe to the former.