Item 15: Understand all your communications options



Item 15: Understand all your communications options

Java communications APIs can be classified along three axes of interest: transport, format, and communication patterns.

Every communications API must move across some sort of communications layer, what we call the transport layer. While virtually all of them end up traveling across TCP/IP (or, less frequently, its connectionless partner, UDP/IP), many of them take advantage of higher-level protocols that build on top of TCP/IP; one such example is everybody's favorite transport, HTTP. Every transport channel has its own unique aspects, however, so it's still worthwhile to differentiate between a communications API using raw TCP/IP as its transport and one that makes use of HTTP—for example, firewall products that can scan HTTP traffic will be happier working with a communications API that uses HTTP as its transport than one that uses raw TCP/IP.

If necessary, Java can be extended to make use of other transport layers via JNI or the java.nio "New I/O" channels. For example, it's sometimes faster to make use of other operating system–specific IPC APIs, such as pipes, named pipes, and shared memory, than to go through TCP/IP. Some channels provide additional behavior, such as encryption, over traditional channels—for example, HTTP over SSL provides a "secure" HTTP channel, what we of course call HTTPS. The ability to move across a different channel is often exposed as a hook point (see Item 6) at the API level of the communication library.

In order to pass data across a transport, the data needs to be in some wire-friendly format. Typically this means we need to either make sure only primitive types are passed across the wire (which are easy to turn into a wire-friendly format) or else turn what we see as fully formed objects in a nice spider web of object references into some kind of flat array of bytes that can be reconstituted into the spider web of object references on the other side. This is known as marshaling, and it's typically (although not always) the responsibility of the communications plumbing to do the data marshaling for you. The data, once marshaled, is often referred to as the payload, and it usually consists of both the data the programmer passes and additional information needed by the communications plumbing (which is sometimes referred to as the framing data).

Two popular marshaling formats in Java are Java Object Serialization and XML. Serialization is popular since it provides all the behavior necessary to turn an arbitrary Serializable object into an ObjectStream without modification to the object in any way—all you need to do is implement the Serializable interface, and off you go. More importantly, Serialization is a completely lossless process. Doing the complete round-trip from object to serialized format back to object guarantees no loss of data. When marshaling into XML, a variety of formats are possible, but more and more XML marshaling is being done via the Simple Object Access Protocol (SOAP), or more recently, XML Schema, using schema types to define what the marshaled data should look like. Other marshaling formats are in use throughout the industry, such as CORBA's Internet Inter-Orb Protocol (IIOP) or the RELAX/NG XML Specification, and some formats remain entirely proprietary and closed.

At the TCP level, all data sent over a socket is broken down into packets of data that are sent over the IP network and reassembled at the destination to be turned back into the original stream of data. Over time, however, we've come to rely on several abstractions—patterns of network communication—that help shield us programmers from the ugly realities of network communication.

Two basic approaches to network communications have emerged. One is the now-familiar Remote Procedure Call (RPC) model, in which a programmer makes what looks like a local method or procedure call, leaving it up to the communications plumbing to marshal the parameters, send them over the transport, block until a response is received, unmarshal the response, and return the unmarshaled data (or throw the unmarshaled exception, if that was the result) to the caller. (This is why most RPC-style toolkits require a postcompilation step, such as RMI's rmic, which generates the local classes—often called proxies or stubs—that do all this work.) More generally known as request-response communication, RPC has found much favor with the programming community due to its conceptual familiarity: "I just call this method, and the rest is all magic until it gets to that method implementation over on the server."

Fundamentally, however, the RPC request-response model is just one of several lower-level communications patterns built on the notion of "sending a message": in the request-response model, a sender sends a message to the recipient (the request, consisting of the marshaled parameters) and blocks until the recipient sends the expected message back (the response, consisting of the marshaled return value or fault code). Other (non-RPC) forms of request-response include the HTTP protocol itself, SQL, and even Telnet.

When viewed this way, however, it becomes apparent that communication has more possibilities than just "send a message, block, receive a message." For example, I could send a message without blocking, send a message and expect zero to many messages back, send zero to many messages without expecting a response, and so on. In essence, we're just describing different ways to send a message.

This concept of sending a message and the inherent flexibility that comes with it are what messaging communications APIs provide. While typically a bit more difficult to work with, in that more supporting code on your part is often required, messaging offers a number of capabilities an RPC-based request-response model cannot. Three such additional "patterns" of communication include solicit-notify, in which one party asks another party to send notifications (such as how electronic mailing lists work); fire-and-forget, also known as one-way or asynchronous calls, in which one party sends a message without waiting for a response; and asynchronous response, in which one party sends a request message expecting a reply but doesn't block waiting for the response, which comes in later. Some messaging systems also support the idea of broadcast messages, in which one message is flung out to multiple recipients.

While messaging itself is a fundamental low-level networking concept, the idea of messaging and its commensurate flexibility has proven powerful enough to merit moving this approach to network communications up to the same level of abstraction as RPC. As a result, we can talk about messaging systems, or message-oriented middleware, which provides this same kind of functionality but at a higher level of abstraction—the Java Message Service, for example, is a specification that defines a standard Java API for working with such systems and defines how to send a message whose payload is a Serializable Java object, simple byte array, a String, a Java Map-implementing object, and so on.

Having defined these two basic approaches to communications (RPC and messaging), we can go one level higher and begin classifying different architectural styles of network communications. A client/server architecture, for example, has one process—designated the server—that will be available to process requests on behalf of a process that initiates communication with it—the client. In a peer-to-peer architecture, however, generally there is no designated server, and processes communicate with one another freely, either side initiating the communication as desired. (See Item 16 for more on peer-to-peer architecture and discovery.) Note that neither client/server nor peer-to-peer architectures are inherently RPC-based or messaging-based; either one can make use of either approach just as easily.

To show how the communications APIs supported by Java break down along the three axes of interest—transport, format, and communication patterns—let's examine each API in turn.

  • Remote Method Invocation (RMI): Following an RPC approach, RMI uses the Java Object Serialization layer for marshaling the data and, by default, sends the data over a raw TCP/IP socket. Later, however, a variant of RMI was introduced as part of JDK 1.2, called RMI/IIOP (pronounced "RMI-over-IIOP"), designed to provide interoperability with CORBA servers, which use IIOP as their marshaling layer. Essentially, it's just RMI, with the marshaling layer using IIOP (a CORBA-specific binary format) instead of Java Object Serialization. As part of later revisions, RMI opened up its transport layer, allowing programmers to write custom socket factories, essentially allowing RMI programmers to provide their own custom transport layers if desired. The most typical use of this functionality is to create an SSL transport for RMI, thus giving RMI a measure of confidentiality that it otherwise lacks. Communicating with an EJB 2.0 or earlier system is almost always done using RMI, specifically RMI/IIOP; some vendors offer their own form of RMI (BEA offers its T3 protocol-based version of RMI, for example), but using this is outside the boundaries of the J2EE Specification and won't be understood by other vendors' systems. (Note that as of the EJB 2.1 draft specification, however, there is no vendor-neutral way to do any kind of SSL-encrypted form of RMI against an EJB server, so you may have to fall back to vendor-proprietary extensions if security inside the firewall is a concern.)

  • Common Object Request Broker Architecture (CORBA): CORBA was the "other" technology staring down Microsoft's Distributed COM architecture in the late 1990s, and Sun made CORBA support ubiquitous within the Java community with the release of JDK 1.2, by bundling a CORBA Object Request Broker (ORB) as part of it. CORBA, like RMI, is an RPC approach, using IIOP as its marshaling layer and TCP/IP as the transport. CORBA supports other marshaling-plus-transport combinations, but none have gained any predominance since IIOP was introduced.

  • Servlets: Servlets are built around a request-response communication pattern, of which HTTP is the most popular (and arguably only) protocol. Unlike most of the other communications systems we'll be talking about in this chapter, servlets require that programmers do the marshaling of the data by hand, by writing the request data to the body of an HTTP request, and writing the response data via the println methods of HttpServletResponse. Of course, coupling servlets with an XML payload format takes you squarely into the realm of Web Services.

  • Java Message Service (JMS): The JMS API is fairly obvious to classify in our three-tuple taxonomy: it's all about messaging, usually using TCP/IP as the transport and the Java Object Serialization format as the marshaling layer, although several JMS vendors now offer the ability to use other transports and/or marshaling layers. Once again, coupling JMS with XML message payloads takes you squarely into Web Services.

  • Jini: Although not part of the J2EE family, Jini offers some powerful and enticing capabilities to enterprise Java projects, most notably the idea of self-healing networks and service discovery, both of which are more fully discussed in Item 16. Modulo the discovery-based lookup capabilities, Jini is really just an exercise in binary-marshaled RPC communications over straight TCP/IP. (A lot of interesting services, such as JavaSpaces, were built on top of Jini, but that was mostly an implementation detail—for example, IBM's TSpaces is another spaces-based toolkit, as JavaSpaces is, and TSpaces doesn't require Jini in any way.)

  • JXTA: Also not a part of the J2EE family, Project JXTA focuses on providing cross-platform peer-to-peer capabilities by operating over TCP/IP and/or HTTP channels and using XML as the wire format. Each peer can send messages to any other peer it discovers in the collection of visible peers—no request-response format is assumed. Later versions of JXTA have created a socket-like abstraction on top of the JXTA plumbing.

  • Java API for XML RPC (JAX-RPC): The JAX-RPC classification is also fairly obvious: RPC communications using XML marshaling, typically over an HTTP transport. Note that the Web Services bindings introduced as part of the EJB 2.1 Specification pretty much fall directly into this category, since both JAX-RPC and the EJB bindings use Web Services Description Language (WSDL) documents to describe their endpoints, just as CORBA uses its own Interface Description Language (IDL) to describe an object's interface and RMI uses Java interfaces.

  • Java API for XML Messaging (JAXM): Built more or less directly over SOAP, JAXM is a message-based approach, using XML for its marshaling layer and typically using HTTP as the transport. Both the SOAP and JAXM Specifications talk openly about using Simple Mail Transport Protocol (SMTP) as another transport, however, which would provide non-request-response (i.e., asynchronous) options. (By the way, JAXM seems to be on its last legs as a specification— several notable voices from Sun have commented via public forums and newsgroups that JAXM doesn't seem to serve any real purpose and, more importantly, despite its presence in early drafts, JAXM is not part of the J2EE 1.4 Specification.)

As you can see, the Java enterprise programmer (that's you) has a widespread set of choices for slinging data around between computers in a network. Which, of course, raises a question: How on earth are you supposed to decide?

After all, any of them will ultimately get the job done—moving data from machine A to machine B—so obviously the decision has to be rooted in something other than just "will it work?" Any of these would work. The larger question is, "Which of these will work well for what I/we need to do?"

Much of the decision-making process lies in identifying, to yourself, the particular context in which your communication needs to take place. Consider the following questions.

  • Do you need to communicate across firewalls? Before you answer this definitively, bear in mind that this doesn't imply going outside of the corporate network—many companies are starting to segregate their internal networks, practicing a "defense in depth" strategy, and putting up firewalls between. Firewalls tend to favor HTTP as the main transport channel, since HTTP is a well-known format the firewall can inspect and scrub. Firewalls also really screw up any sort of client-driven RPC back against the server (which many Observer-style [GOF, 293] callback notification schemes tend to rely on) because they won't allow incoming traffic on arbitrary TCP/IP ports, toward which both RMI and CORBA tend.

  • Do you need to communicate synchronously? If most of your communication patterns are of a request-response style, RPC certainly stands out as a preferred communication pattern choice—the messaging-based style permits greater flexibility but generally requires much more work on your part.

  • Do you need to be able to communicate to any platform, including those you don't know about yet? Web Services (i.e., XML-based payloads over standardized channels like HTTP) create a "middle ground" between all platforms, thanks to XML's ubiquity. But, as described in Item 43, XML comes with its own costs, which may be more than you're really willing to incur for interoperability between just two platforms (.NET and Java). More importantly, you need to make sure that you're taking all those other platforms into account when you build your communications stack in a Web Services–based model (as described in Item 22).

Certainly, there are other questions we could ask. We could even create a giant decision-making flowchart of communications technologies, but doing so starts to encroach on the value judgments that each architect and system designer will want to make differently. The point, simply, is to consider your communication needs carefully before committing to anything. (And remember, assuming you've used components in your system well, per Item 1, you can usually change and/or add new communications strategies without too much hassle.)