By the time object technology grew robust enough
to take on the job of building distributed systems, three-tier had become
established as the architectural model of choice. The consensus held
that nothing less could handle the demands of scalable, mission-critical,
heavy-duty applications. In order to compete in the big leagues, object
systems adopted three-tier terminology almost automatically, and we have
seldom looked back.
(We pause here to clarify what we mean by
“three-tier,” so fuzzy has the term become. We are talking here about
an architectural definition of three-tier, the division of an application
into pieces that perform, by one name or another: 1) presentation to the
user, 2) execution of business logic, and 3) access to the database. For
purposes of this article, we ignore the opportunistic, vendor-driven uses
of “three-tier” to indicate how many machines the application spreads itself
over, or on which platform any particular piece executes.)
We believe that implementing “three-tier architecture”
using object technology should not mean simply recasting, in objects, the
same divisions and relationships that we saw in non-object systems (which
we call here “procedural” or “classical” systems). The whole notion of
tiers takes on quite a different flavor in the object world. For one thing,
objects let us decide with more precision which part of the system should
assume responsibility for which activity. Different tiers hold different
kinds of objects, so tier assignments can be made, in general, on the basis
of pedigree rather than by making often-arbitrary judgements about whether
something is business logic or user-interface logic.
Even more important, in an object system the
nature of the tiers themselves shifts. Behaviors get refactored and
end up distributed among architectural categories significantly different
from those in the procedural world. The differences are so profound, we
hope to show, that one whole tier disappears, and a new one—populated by
an entirely new kind of object—emerges.
Two-Tier OO
If we try to compare the old User Interface/Business Logic/Data Access
triad with what happens in object applications, we see both correspondences
and differences. The front tier stays roughly the same. What is usually
called the Presentation tier in non-OO systems corresponds closely to what
we call “consumer-interface objects.” Typically these are “viewers” or
“view objects” that render the non-visual objects found in other tiers
of the architecture. We call them “consumer-“ rather than “user-“ interface
objects because, increasingly, they need to support not only humans using
GUIs or browsers but also non-human programmable agents at work on various
system assets.
If the front end is similar, the back end
is quite different. OO tends to take business logic that was previously
located in Tier 2, and persistence logic formerly found in tier 3, and
combine them into back-end entities called “domain objects,” which model
the fundamental assets of the business (Employee, Part, Subscriber, Account,
etc.) After all, object thinking encourages us to factor entities according
to the application domain they belong to rather than the specific kind
of behavior they incarnate. So, for example, an Employee object may encapsulate
behavior for validations and transformations (“business logic”) as well
as for accessing and persistence (“data access”).
Some object architectures, it is true, explicitly
picture a data access tier behind the domain objects tier. The objects
here, according to this theory, depict the persistence machinery for mapping
domain objects back and forth to the datastores at the far back end. While
we acknowledge that there is no right way or wrong way to create architectural
diagrams (they are, after all, merely tools to help us organize our thinking),
we believe that this extra tier is neither useful nor logical. First, such
persistence machinery seems to us part of the architectural infrastructure,
just as transactions, security, and naming are. It doesn’t materially add
to the way we think about the structure of the application. Second,
some environments (such as in an object database) actually include persistence
in their object model; in these cases, a separate layer becomes superfluous.
Finally, adding “data access” to an OO application architecture is something
of an architectural mixed metaphor. After all, “data” per se is not supposed
to exist when we have objects everywhere.
We would maintain, then, that the essential
elements of most of today’s distributed object systems today are viewer
objects on the front and domain objects on the back, wired together with
a bit of hand-coding in the middle. These are essentially two-tier systems,
though few would care to admit it.
This conviction that we are implementing three-tier
systems when we actually aren’t has kept us from coming up with a fresh
three-tier model that is truly suited for how objects behave and what we
can do with them.
What makes us think that there is an as-yet
undiscovered application structure here? Because distributed object
systems are harder to build than we expected them to be. That “little
bit of hand coding” referred to above turns out to be large stretches of
application-specific code written from scratch without even standardized
procedures to follow. Wiring together the two tiers of these applications
has in practice consumed probably as many developer hours as the creation
of domain and view objects put together. As any builder of models knows,
when the layer of glue becomes as thick as the things it’s holding together,
you have to wonder if there isn’t a piece missing in the middle.
What is missing in this case is a bona fide
middle tier, unique to object systems and quite unlike the old, vague “business
logic.” It would be the home of a new kind of object, one that stands between
view objects and domain objects and performs a newly-defined job of handling
mediation and communication between them. If we can accurately understand
the function of this new middle tier, find the right name for the objects
that live there, and come up with state and behavior to get the job done,
we can uncover opportunities for reuse in a large application stretch that
currently has none.
Three-Tier OO
How can we generalize about what goes on at
the middle tier of any application—NewHire, TransferInventory, ScheduleShipment,
DispenseCash or what have you? We suggest the following: This is
the place where the intentions of the client side are formed into requests
that will subsequently be handed to the domain side.
These requests have a function and a personality
that is not like anything we have seen before.
We call them Proposal objects, for their job
is to “propose” changes to enterprise assets. When they join the other
two kinds of objects in a new OO triad, system responsibilities suddenly
fall into a three-tier division that is so logical it seems to have been
waiting for us all along. We have domain objects, which represent enterprise
assets; Proposal objects, which represent proposed changes to domain objects;
and viewers, which offer a way to look at and interact with either.
The Role of Proposals
A Proposal object abstracts the common behavior
involved in putting together requests for updates to back-end domain objects.
Its infrastructure should handle organization and navigational behavior,
and handles the bulk of the mapping between the input gathered via consumer
interface objects and the information required by domain objects.
It could also include advanced transaction-forming behaviors such as document
navigation, error-and annotation-handling, versioning, archiving, and stale-data
detection.
A good way to understand the power of Proposal
objects is to contrast them with domain objects. Domain objects are
definitive. They represent committed enterprise assets such as Employee,
Part, Subscriber, Account. They have to be correct, complete, authoritative,
in synch with one another, and durable (that is, so constructed that they
can survive machine failures).
Proposals, by contrast, model unfolding, often
multi-step and multi-party processes. Examples of Proposal objects
are NewHire, CreateInvoice, MeritIncrease, ResubscribeUser. Since they
are only tentative suggestions to modify enterprise assets (these
suggestions will have to pass all enterprise screens before they are committed),
Proposals can be fallible. They are meant to be worked on, refined, changed,
passed around, reviewed, and shaped over time. They bring “mulling-over”
and “trying-out” space into a system.
Thus, Proposals can be imperfect and incomplete.
They can tolerate errors, noting and marking them as a kind of “to-do”
list. They can be disconnected from the authoritative store of domain
objects. They are kept in their many successive versions— they are
undo-able and redo-able—with the version list being available for detailed
auditing purposes. They can carry annotations around with them, as vehicles
for discussing—we might say, negotiating—eventual transactional requests.
In short, they are very much like proposals in the human world—concrete
“things” that embody the process of collaboratively formulating something
that may or may not eventually become binding.
In order to do the job we have laid out for them,
Proposal objects have a number of responsibilities, implemented as state
and behavior. Most important, Proposals must be introspective, and know
a great deal about their own metadata. They have to act as containers for
the data related to a specific Proposal. They must keep versions of themselves.
They should retain and track errors noted by the infrastructure, and also
manage user-written annotations. They need a mechanism for protecting against
stale data. In addition, Proposals must know how to interact with the back-end
server, whatever it may be, and, on the other end, to interact with a variety
of consumer platforms.
We have said that Proposals occupy the “middle”
of our re-envisioned OO three-tier architecture. This means only that they
live somewhere “behind” the consumer objects and “in front” of the domain
objects, but we’re left with a wide range of possibilities. With
appropriate middleware connecting them to their architectural neighbors,
they can live anywhere. In fat-client environments they might live at the
workstation. In thinner-client environments they might live at the server.
They could occupy a Proposal server of their own.
In fact, they can move as needed. This is
a new development in distributed object design, which has in the past focused
on relatively immobile objects that are essentially locked to a particular
platform (domain objects hang out close to a database somewhere, viewer
objects cluster near the glass). But Proposal objects have no geographical
allegiances. A Proposal object can be initiated on the server, routed to
client for further work, routed sideways to collaborators for further input
and refinement, worked on by non-human agents, and then routed back to
domain object to commit proposed changes.
This is one reason why Java, with its ability
to support mobile objects, is the ideal development platform. In fact,
the idea for Proposals has taken final shape only since the advent of Java.
When To Use Two-Tier vs. Three-Tier
Clearly the Proposal-object middle tier adds a
new level of power to an application architecture, and also a new level
of complexity. When is the added investment justified?
Two-tier architecture—the viewer/domain duo
described early in this paper—will serve well when:
-
The application is a simple viewer or editor of the domain objects.
A kiosk in a video store may simply need to get at a collection of movies
available and filter them based on user preference.
-
The transaction being performed is innately “conversational.” This
is where the user is connected to the domain objects from the moment that
the transaction starts to the moment that it completes: an Automated
Teller Machine begins and finishes its job while the user stands in front
of it.
Three-tier architecture including Proposals adds
a great deal of infrastructure for handling more demanding business processes.
It will be invaluable when:
-
The transaction is collaborative. In reengineered environments a change
to domain objects is also the basis for horizontal communication within
the organization. For example, a manager might initiate a MeritIncrease
Proposal complete with suggested new salary level, annotate it to give
his reasons for that raise, and forward it to a superior for discussion.
-
The transaction is completed in phases. E-commerce is a good example here.
A subscription-renewal proposal (for example, renewing a software support
agreement) could be partially filled out by the support company and emailed
to the customer. The customer can open the Proposal, choose the renewal
term, and email it back to complete the transaction.
-
The transaction needs to be accessed by many kinds of consumers.
A customer service request my be created by caller-ID equipment and immediately
transferred to a service rep’s terminal screen for follow-up.
-
The transaction is interruptible. A lengthy multi-page Proposal (for example,
a NewHire transaction) can be pended, saved, and continued on the following
day.
-
Detailed audit trails are needed. Most current systems keep audit trails
once transactions complete. But Proposals can log much more detailed information,
such as which of the Proposal’s users changed which field when. This
information remains available even if subsequent users further changed
the field before the Proposal was committed.
-
The application supports componentized construction. Since the Proposal
carries lots of metadata about itself, its fields, its structure, and so
forth, it works very well with “Proposal-aware” components like Java Beans
or ActiveX controls.
Adding Workflow
Looking at the list of applications that call
for three-tier OO, it’s easy to see why Proposal thinking leads directly
to workflow (or vice versa). The synergy between the two is so irresistible
that many people confuse them on first encounter. Actually, the difference
is quite simple. Workflow is the train that routes content through your
organization; Proposals are an architected, reusable way of defining that
transactional content—the containers that the train carries, if you will.
We like to depict workflow as a separate layer
that, in an architectural chart, would appear over the consumer-interface/Proposal/domain-object
tiers. We call the objects in this box “FlowControllers” to give
them an agent flavor.
In general, the FlowControllers are used to dispatch
and route Proposals. When a Proposal is finally committed, the domain objects
get updated and the flow is completed. There are interesting possibilities
for cascades here: the domain object, when changed, can actually generate
new Proposals and hand them back to the workflow system. For example, a
NewHire Proposal has been filled out and committed. As soon as the resultant
new Employee domain objects is created, it might fire a business rule that
causes several new Proposals (such as BenefitsEnrollment, ParkingSlotRequest,
and PhotoBadgeAppointmentRequest) to be generated and routed to the appropriate
locations.
Conclusion
Object technology’s insistence on finding the right name for behavior is vindicated
once again. Adding objects called “Proposals” to distributed object
systems solves a number of problems and makes the entire 3-tier structure
snap into new focus. Just as they do in the human world of buying, selling,
delivering work for pay, arranging complex deals, and even deciding
to get married, Proposals provide a needed intermediate step between
a rigorously controlled and highly valued assets on one end, and free-spirited
consumers with ideas for changing those assets on the other.
We are at the right moment in the development
of distributed object computing to look carefully at application structures.
The infrastructure issues have been by and large settled, and now we
are getting down to real work. As we might have expected, the most useful
model for robust transactional systems turns out to be a three-tier
one after all. They just aren’t the three tiers we thought they
were.
# # #
|