Objects versus documents for server-client interaction, Part 2
Determining the best approach when there is no client-side user
In Part 1 of this series, I demonstrated two approaches to defining the interaction between a client and a newsfeed server: using documents and protocols and using objects and interfaces. Now I’d like to compare their relative merits. I’ll discuss the advantages of objects as compared to documents, then the disadvantages.
TEXTBOX: TEXTBOX_HEAD: Using objects versus documents for server-client interaction: Read the whole series!
- Part 1. Comparing two ways software can interact with software
- Part 2. Determining the best approach when there is no client-side user
-
:END_TEXTBOX
The advantages of objects
In the following sections, I will identify a few advantages of using objects for client-server interaction.
Raising the level of abstraction
One obvious difference between the object and document approaches is that developing newsfeed protocols involves network handshake and data-model design, whereas developing the newsfeed API requires object-oriented design. The object approach enables client and server programmers to work at a higher level of abstraction. To explain, I’ll relate a story about Bjarne Stroustrup, creator of C++.
I first learned C++ in early 1992 from Borland’s World of C++ videotape, which includes a short introduction by Stroustrup. He begins by saying the main goal of C++ was to raise the level of abstraction in programming. As I watched the tape, I was able to grasp the object-oriented concepts and C++ syntax, but I was puzzled by Stroustrup’s comment. I was unsure of what he meant by “raising the level of abstraction” and why he found it desirable.
In April 1999, I attended a small C++/Java conference in Oxford, England, at which Stroustrup gave a keynote. In the speech, he cited two ideals for programming: keep independent things independent, and work at the highest level of abstraction you can afford.
Afterwards, I asked Stroustrup why working at the highest possible level of abstraction was a fundamental programming ideal. He said it allows details to be hidden, which makes code shorter. “The more details, the more mistakes,” he said. Code size also impacts maintainability: the more code you have, the more difficult it is to maintain. Stroustrup also said that at higher levels of abstraction, code is more amenable to tools that analyze and optimize. He summed up by saying that code written at a higher level of abstraction is easier to understand, write, and maintain.
I believe the object approach is bolstered by the programming benefits, outlined above by Stroustrup, of working at a higher level of abstraction. To a great extent, programmers who switch from procedural to object-oriented programming reap the same benefits. When you write a client program that interacts with a server via an object sent across the network, the advantage of using object-oriented programming in your distributed system is obvious.
Protocols become implementation
Object-oriented programming cleanly separates interface and implementation. One of the main strengths of Jini’s architecture is that communication between a Jini service object and a server is part of that object’s implementation. Although a client and server must agree on a network protocol in order to send the first object from server to client, client/server communication can take place via object interfaces once the first object is in place.
Unlike an object, a document cannot make a socket connection back across the network. Thus, for clients to interact with documents received from a server, the client and server must agree on the document’s data model as well as a protocol. For example, if a user fills out a form contained in an HTML document and presses Submit, the Web browser opens a socket to the Web server and most likely issues an HTTP POST command containing that information. The Web server processes this POST and returns another HTML document. If a client program wishes to interact with the server via documents instead of just silently consuming documents, the client and server must agree on protocols beyond the initial protocol that gets the document to the client.
Once a Jini service object has been downloaded into a client, the client and server need only agree on that object’s interface. The network protocol, which is part of the service object’s implementation, can vary by service vendor and change over time. (Because the service object is sent across the network from the service provider to the client, it will always be up to date, be compatible with the service provider, and speak the correct network protocol.) The network is one of the fussiest and most dynamic parts of the hardware being programmed, so having the ability to choose the most appropriate protocol for each situation is important.
Evolving the contract
Another basic difference between the object and document approaches is the nature of the client/server contract. For network-mobile documents, the protocol (including the data models of any documents) forms the contract. For network-mobile objects, the object’s interface forms the contract. One important advantage of objects is the ease of changing their contracts over time.
Coming up with a good design, whether for an API or a DTD, is difficult. Figuring out, agreeing upon, and communicating the design requirements is often a significant challenge. So is determining the best way to model the functionality (for an API) or information (for a DTD) that the requirements demand. And then there is perhaps the most daunting task of all: predicting the future.
Requirements evolve over time. Consequently, a design’s ability to gracefully accommodate future changes is usually an important quality. But how do you achieve this when you don’t know what changes will occur? Mark Johnson, JavaWorld’s XML columnist, discussed the difficulty of anticipating evolution in XML DTD designs:
It’s difficult to get a DTD right because it’s difficult to get a data model right, and a DTD is a data model. The hardest thing about getting a data model right is that data models evolve. There’s “right” for today, and “right” for tomorrow. But you don’t know what’s “right” for tomorrow up front, so you have to guess. Data modeling is, in part, the art of including enough generality to anticipate future schema changes without kicking the model up to such a high level of abstraction that it’s inefficient or incomprehensible.
Fortunately for API designers, Java pays close attention to the need to improve contracts. The seldom-read “Chapter 13: Binary Compatibility” of the Java Language Specification describes how a Java class or interface can change without breaking binary compatibility with client code compiled by an earlier version. Similarly, the Java Object Serialization specification provides mechanisms that enable the exchange of serialized objects between parties familiar with different versions of the objects’ classes. Java’s support for versioning of classes, interfaces, and objects helps to evolve requirements over time. XML, by contrast, does not offer much help with evolving requirements. On the topic of evolving DTDs, Rusty Harold, author of XML: Extensible Markup Language and creator of Cafe con Leche, an XML developer Website, (see Resources for a link) said:
XML does not provide any explicit versioning support. You have to do it all yourself. However, [if you write a DTD in 2000 that validates year 2000 tax return XML documents,] it is fairly easy to write a new DTD in 2001 that will validate both 2000 tax returns and 2001 tax returns. Writing that DTD in 2000 or 1999 is a little harder.
Data-model designers typically allow for change by specifying in the initial data model some abstraction that can be ignored if not recognized. For example, the Java class-file format (a data model for defining Java types) includes a data entity called an attribute. Each attribute’s name indicates its structure and semantics. The original Java Virtual Machine (JVM) specification defined the structure and meaning of several named attributes, and identified attributes that JVMs are required to recognize and use. The specification also stated that:
- The use of any other attributes is optional
- Any party can define new attributes, as long as those attributes don’t affect the semantics of the class or interface described by the class file
- New attributes defined by anyone other than Sun must be named via a reverse domain name convention to prevent name conflicts
- JVM implementations must silently ignore attributes they don’t recognize
The attribute abstraction of the Java class-file format enables the class-file data model to evolve in such a manner that class files containing new attributes can remain backward compatible with old virtual-machine implementations that don’t recognize the new attributes. Designers of an XML DTD or other data model can incorporate a similar abstraction to accommodate future changes in requirements.
Dealing with evolving requirements can be challenging when working on a monolithic application, and even more so on a distributed system. In a monolithic application, you can adopt a new contract that is incompatible with the old and update the old contract to understand the new one. For example, in a monolithic Java application, you can break an interface’s contract by changing the name of a method in that interface. If you recompile the entire application, however, the compiler will find all the code broken by your change. If you update those areas of the code to use the new method name, your application will compile.
In a distributed system, on the other hand, you often can’t update code that is broken by a change to a contract. In a public system, such as Moreover.com’s newsfeed, you don’t control all of the system’s pieces. In fact, you don’t necessarily know who controls each piece. Even if you did control the entire system, it would probably be physically impossible to update every piece quickly enough to satisfy your users.
In a distributed system, once a contract has been established between a client and server, you generally can’t break it, but usually still need to evolve it. This is why Java’s support for versioning of classes and serialized objects is so important — Java’s versioning support lets you evolve an API that forms a client-server contract without breaking parties that are familiar only with the contract’s previous version. This is one of the major benefits of mobile objects.
Disadvantages of mobile objects
Now that I’ve identified a few advantages of using the object approach for client-server interaction, I’d will discuss some of the disadvantages.
Client-side overhead
One potential disadvantage of the object approach is the amount of client-side overhead required to receive and use a mobile object. To host a Jini service object, for example, a client needs a JVM and a significant set of Java APIs. If your client only needs to deal with one or two kinds of services, taking the protocol approach will likely yield a client program with a smaller footprint. Why? Because the footprint of a client taking the object approach will, by definition, include the footprint of some incarnation of the Java Platform.
If your client’s host machine has sufficient resources (those of a PC or workstation, for example), selecting and installing an implementation of the Java Platform is quite straightforward. In the future, however, more embedded devices will be connected to networks, and will want to offer and consume services across those networks. The economics of many embedded devices are such that miniscule changes in unit cost can represent a substantial amount of money. As a result, there is usually great pressure to keep the computation power of an embedded device at a minimum. If an embedded device can get by with speaking just a handful of protocols, the protocol approach will probably be deemed more economically viable than the object approach. (In this discussion, I am only considering the economic effects of the technical differences between the object and document approaches. Embedded device vendors interested in Jini should also worry about signing a commercial agreement with Sun in conjunction with the Sun Community Source License (SCSL) and paying the required logo fee.)
If you are developing an embedded light-switch device, for example, and you want to offer a simple on/off-switch service across the network, it would probably not make economic sense to put a JVM in that light switch. Instead, your light switch would speak a protocol.
Yet with a little help, a protocol-based service could still participate in a Jini federation. For example, a Java Platform could reside somewhere on the network to which the light switch is connected, perhaps in a set-top box or some other central host or gateway. On top of that Java Platform, some software (a surrogate) could serve as an object-oriented front end to the light switches. Through this surrogate, clients and other services could use object-level interfaces to talk to the light switches, which could talk to the surrogate via a network protocol. The surrogate project at Jini.org is currently working to define a standard way to bring protocol-based services to Jini federations using such an architecture.
Denial of service
Another concern raised by the mobile-object approach is the increased potential for denial-of-service assaults, whether intentional or accidental. Although the Java Platform has an extensive security model to prevent untrusted code from taking many actions that could represent security threats, the model does not guard against certain denial-of-service attacks. For example, a network-mobile object could fire off threads, allocate memory, or open sockets until client-side resources were fully consumed. Or, network-mobile objects could neglect to release the fair share of client resources they legitimately claimed, causing a gradual resource leak.
Denial-of-service attacks are generally difficult to guard against. Even the protocol approach has potential avenues for these attacks. For example, a Web server could serve up an infinitely long page that would fill up the client browser’s cache. Or, a server could be kept so busy dealing with fake client requests it would have no time to handle real ones. But the mobile-object approach, because it involves injecting code into a client, is much more susceptible to creative denial-of-service attacks than the mobile-document approach. This makes it rather foolhardy to pull mobile objects into certain kinds of environments, such as mission-critical servers.
Despite Java’s security model, mobile objects are still a greater security risk than mobile documents that don’t contain executable code. Although Java’s security model has extensive flexible mechanisms to protect client-side systems from malicious or buggy mobile code, some implementations of the Java Platform have contained bugs that opened up security holes. Although these bugs have historically been fixed promptly and no actual damage has ever been done, the possibility of future bugs increases the risk of hosting mobile objects. The complexity of defining a security policy that is more lenient than the standard applet sandbox also increases risk. For some security-sensitive environments, the potential for bugs in the security model implementation, as well as human error in configuring it, may disqualify the mobile-object approach.
The test matrix
A third disadvantage of the object approach involves testing. My last C++ contract was with a company that provided a distributed system that enabled insurance companies’ servers to exchange data with clients at general agents. Over the years, this company had switched technologies several times. Customers are naturally hesitant to upgrade a system that is already working, especially if they have to pay for the upgrade, so this company was still supporting just about every client and server incarnation it had ever shipped. When it came time to test a new software release candidate, the software quality assurance (SQA) department would pull out its test matrix, a two-by-two matrix with clients on one axis and servers on the other, to plan the test. If the release candidate was a client, SQA would have to test it with every possible server it might be used with. (OK, to be honest, the department didn’t usually get to test the whole matrix. They wanted to — but that’s a different story.)
One of the challenges the mobile-object approach presents to distributed computing is that when objects can glide so effortlessly across networks, it is almost impossible to know where an object you are sending might land or where one you receive may have originated. The problem is not the size of the test matrix, but its lack of definition. So how will you ever achieve robust, reliable, object-based distributed systems if you can’t test the objects’ performance in all possible environments?
In the world of mobile objects, you simply can’t test every possible client/server combination. You can, however, build a test matrix with a known, hopefully representative, subset of combinations, and test those. You can also do standalone tests for compliance with the contract that the client or service is supposed to adhere to.
I think one key to achieving robust and reliable distributed systems, whether object- or document-based, is a well-defined contract. Just as with mobile objects, you often won’t be able to test a protocol-based server for every kind of client, but you can test the server’s compliance to the protocol specification. For meaningful compliance tests in either approach, the specification of the contract you are testing against must be detailed and complete.
In addition to well-defined written specifications, good compatibility test suites are important. Whether you are writing an implementation of a service API or a client, a test suite to identify areas where your client or service departs from the specification is a valuable tool. If a compatibility test suite isn’t included with the specification and javadoc documentation for an API, then the documentation will likely be interpreted in many slightly different ways, resulting in unreliable distributed systems.
Server scaling
A fourth disadvantage of the object approach is its limited scaling capacity on the server side. To ensure that a server can concurrently handle large numbers of client requests, a programmer needs to control the allocation of finite resources such as threads, sockets, and file handles. If each client interacted with the server through its own mobile object, the server couldn’t be certain how many threads, sockets, and file handles each of those client objects consumed, or when they consumed them. This is why in the standard Jini approach, the client receives a mobile object from the server, not vice versa. The client interacts with the service via the object interface. The server, by contrast, interacts with its service object (which is running in the client) via a network protocol.
The trouble with the standard approach is that we sometimes want servers to act as Jini clients. I recently developed a Jini chat service for Sun Microsystems that, had a laptop not been stolen the night before, would have been demonstrated during the Friday keynote at JavaOne. To participate in the chat service, a client finds it with a Jini lookup service. Clients’ chat service objects exchange messages and other information by writing entries to a JavaSpace. Once the client receives and starts using the chat service object, the object contacts a lookup service to retrieve proxies to a JavaSpace and a transaction manager. The chat service object then fires off several threads to renew leases, monitor messages being posted to the chat room, and monitor people entering and leaving the room.
While working on the service, I spoke with John Whetherill, a Jini evangelist for Sun. Whetherill had implemented a chat program on the wireless RIM pager sold at JavaOne, and we discussed the integration of Whetherill’s functionality with my chat service. The only way to interact across the network from the RIM was via HTTP, so Whetherill had written a servlet that accepted HTTP messages from the RIM, allowing RIM users to chat with each other. Whetherill and I brainstormed ways to enhance his servlet to allow RIM users to participate in the Jini chat service.
My first inclination was to recommend that Whetherill simply turn his servlet into a client of the Jini chat service. The chat service functionality was already implemented in the chat service object, and I figured Whetherill’s servlet could grab a chat service object for each RIM user and just invoke the objects’ methods like any Jini client. The trouble, of course, was that each service object fired off several threads and opened several sockets, and we expected upwards of 500 RIM users to try to chat at the same time. We talked about many different ideas, but couldn’t figure out how to get the servlet-as-Jini-client approach to scale. Ultimately, Whetherill had his servlet interact directly with the JavaSpace, replicating in his servlet much of the code that made up the chat service object.
When servers want to act as Jini clients, I think the way to deal with server-side scaling is to make multiple servers available and somehow distribute incoming mobile objects to those servers, based on their available resources. But I also suspect that servers will often be speaking protocols to clients, or to proxy objects injected into clients.
Download time
One last potential disadvantage of mobile objects is download time. An early complaint about Java applets was that they took too long to download compared to Webpages. Of course, a huge Webpage with lots of gratuitous graphics could easily take longer to download than a svelte Java applet. Download time is determined by bandwidth and amount of data being transmitted, not whether the data is HTML text or byte code.
One reason Webpages are perceived as faster is that entire services are split up among many different pages, each of which downloads individually. For example, when using Yahoo!’s email service, you may end up downloading tens or hundreds of individual pages each session. Users have been trained to wait a certain amount of time for each Webpage. An applet, however, is typically downloaded all at once. After the download is complete, users will usually not have to wait anymore. But even if an applet’s download time is less than the cumulative download time of all of a service’s pages, users still perceive the applet as slower because the wait time is all up front.
The extra time required to download large objects can be managed in three ways:
- The size of objects like Web documents should be kept to a minimum
- Objects can download other pieces of a service on demand, much like Webpages are requested one at a time, as long as users are trained to expect this kind of behavior
- You can use caching to reduce the distance objects must travel across the network, and hence the time the client (and user, if present) must wait for them. (Of course, bandwidth increases will also help address this problem.)
Download time is something that needs to and can be managed, regardless of whether you are downloading objects or documents.
Objects are more flexible
I believe the area where the object approach is most superior to the protocol approach is flexibility. By moving the contract between parties of a distributed system, from the bits and bytes of network protocols to object interfaces’ higher level of abstraction, you gain flexibility in fulfilling and evolving that contract. Because an object interface’s contract is couched in terms of behavior, not information (as with a document’s data model), an object’s contract can be more abstract than a document’s. The more abstract a contract, the more ways it can be fulfilled.
Perhaps the most important flexibility the Jini service object offers is the service provider’s ability to decide what protocol, if any, its service object will use to talk across the network. A few scenarios:
- The service provider fully implements the service locally in the service object itself
- The service provider makes the service object an RMI or CORBA stub that forwards all method invocations across the network to a remote object
- The service object translates client requests received through the object’s interface into the bytes of a proprietary socket protocol understood by the server
- The service object is a smart proxy, which partially implements the service locally, sometimes enlisting the help of a server or servers across the network
In short, a Jini service object gives the service provider the flexibility to choose the best protocol for each situation.
In his article “The End of Protocols” (see Resources for a link), Jim Waldo, Sun’s chief Jini architect, put it this way:
Systems that are based on a protocol … need to fit the needs of all clients and services to the single protocol. The protocol must be completely general, and as is often the case when something needs to be good for everything, these protocols are often less than optimal for particular, specialized communications. Protocol design, like any engineering design, is often a trade-off between efficiency and generality. In systems that are designed around a one-size-fits-all protocol, such decisions need to favor generality. … [Jini’s mobile proxy object] approach gives great flexibility to what protocol is actually used. Different services can invent their own specialized protocols that are optimized for that particular pair of proxy and service. Protocols can evolve over time as new ideas are tried out.
This flexibility in deciding how to implement the service object interface amounts to flexibility in managing the network. You can choose whether to talk across the network, and if you do, what protocol to use. This flexibility is extremely useful when working with distributed systems, because it creates more choices in dealing with the most variable and unpredictable part of the distributed system: the network itself.
To discuss the material presented in this article, visit my discussion forum.
Next installment
In my next
Jiniology
column, which will appear in September, I’ll complete this three-part series with a look at the advantages mobile objects offer when users are involved. Just as XML separates data and presentation, the Jini ServiceUI standard separates functionality and presentation. I will also compare the object and document approaches to interaction with users across the network.