Subject-oriented programming through RMI

Create a shared experience with the 'subject' design pattern

How do you make software that allows a number of people to work together on the same group of things? Think of air traffic control, logistics planning, multiuser games, whiteboarding, or even software development itself. In each of these examples, there is both a group of people and a set of the things they're working on, and what we really want to do is create a little universe in which they can all exist and interact with each other in realtime.

This little universe, of course, isn't new -- it's been accomplished in many ways in many different situations. But at some point we must ask ourselves if there is a more overarching way to solve this recurring problem.

This article describes just such a solution -- one I've built using Java's elegant Remote Method Invocation (RMI). The Java code required to build the necessary infrastructure for this kind of software is remarkably minimal, and the result is extremely flexible.

'Subjects' defined

While writing about this software problem or trying to explain it in the classroom or to my clients, I've found that I constantly use the phrase "objects-of-interest" in order to describe the thing -- whatever it might be -- that a given group of people was working on. Then it occurred to me that these things could simply be called subjects instead, though I knew that this might be a bit of a daring wordplay. (Think: server plus object equals: sobject, or subject.)

If we want to build a sensible environment for a group of people to interact with a group of subjects, there are a number of requirements:

  • Participants should be able to "tune in" to a selection of subjects

  • Everyone must receive quick notification when a subject's attribute changes

  • Update notification should be automatic, or listener-based, as it is in AWT and Swing components

  • Network traffic should be minimized using a caching strategy

Rethinking client and server

Remote method invocation provides a way to build wormholes to join the object galaxies contained in different virtual machines. Effectively, RMI creates special reference "wires" that extend from one galaxy to another, and tries to makes sure that the method-call "sparks" along these wires visit the other galaxy and return intact. There are two important differences between local method calls and RMI: RMI sparks happen in slow motion compared to local method-call sparks, and the intergalactic wires can sometimes go down.

Developers often choose to have RMI's remote objects reside exclusively on a machine they call the server, with their counterpart proxies on the client machine. This is fine until you need to allow the server to initiate an event -- for instance, in collaborative software in which one client's updates must be broadcast to all the other clients. In this context, the client must also play the role of server in that it must be ready to receive updates at any time.

Client/server and the collaborative system

It's time to rethink the ideas of client and server. The key difference between server and client objects in a collaborative system is their one-to-many relationship. The relationship can be nicely described as a fanning out of connections between the singular subject and the multiple remote subjects, which are the things that the people see.

The issue of who is serving and who is clienting is moot, since all we really want is for everybody to see an up-to-date version of the subject. The collaborative whiteboard system built in the December '97 issue of JavaWorld's Step-by-Step column uses a polling approach to ensure that everybody stays up to date. While this is easy to implement, it causes lots of unnecessary network traffic. A nicer solution is to have updates propagate only when they occur.

A further look at collaboration

The essence of collaboration is that the subjects are shared among the participants. Everyone should be looking at the same thing, even if each wishes to see it in a different way. And when one person makes a change in a subject, the other people should see the results immediately -- or as soon as possible.

Collaborative software often demands quick access to the subjects for the purpose of rendering within a single view. This problem is not solved by the remote-observer-callback mechanism because the real data still resides on the server. What's needed is some way to cache the current status of the shared objects so that a repaint causes no network traffic whatsoever. The solution presented here performs a simple and extreme form of caching: we maintain synchronized full replicas of the subjects on each client.

In certain situations, updates will also occur as a result of some server events, not just due to client actions. Server-initiated updates must propagate to all clients.

We want a piece of collaborative software to allow us to tune in to some chosen selection of the available subjects, whether our selection overlaps with that of other people or not. Typically, we won't be actively modifying more than one subject at a time, but we would like to be shown all the changes the others are making as they happen. When you tune in, you also expect to encounter the subject in its current state.

Entering the realm of subject-oriented programming

Once the mechanism of maintaining synchronized replicas of subjects is in place, you're liberated from the complexities of deciding which objects to make remote and which to make local, of how to deal with callbacks, and of how to keep screen updates working quickly. You just have to implement the Serializable interface and decide which things are to be called attributes. You have entered the realm of subject-oriented programming.

To accomplish the creation and maintenance of synchronized replicas on the client and server, we use a pair of partner objects. Each consists of RMI remote objects and maintains proxies to the other. Although these two partners are separated from each other in entirely separate object galaxies (virtual machines), effectively they interpenetrate.

The only fundamental difference between the partners is that the one residing on the server must concern itself with multiple client partners, while the one residing on the client only has one server partner. The paired objects are generic -- they can be applied equally well in any situation requiring the subject pattern. For simplicity, I chose to make the objects vectors, but they could just as easily have been derived from a hashtable or any of the new Java 1.2 collection classes (see Resources).

And now...the code

The entire architecture hinges on one tiny interface called Subject, which defines the basic functionality of subjects both on the server and on the client. Since a subject is something with some given number of attributes, the Subject interface provides access to them in an indexed manner with its get() and set() methods.

Subject The most important thing about this replica object is what the programmer sees of it, as represented by the following interface on both the client and the server:

public interface Subject extends Watchable {
   Object get(int theIndex);
   void set(Object theObject, int theIndex)
   throws RemoteException;
   void dispose()
   throws RemoteException;
}

The dispose() method is needed to explicitly break up the linkage between client and server partner objects, since two remote objects referring to each other can never be garbage-collected on either side.

Notice that the accessor method does not throw a remote exception! RMI programmers will be accustomed to defining interfaces in which each and every method throws a RemoteException, since all method calls represent communication. With the subject design pattern, however, the presence of a replica eliminates communication when accessing attributes. Because we always have a replica on hand, getting information from a subject is a local activity.

Behind the mask of the Subject interface lies one of two different kinds of implementation objects, depending on whether the subject appears on the client or the server side. On the server side, the implementation is called a hub; on the client side, it's called a tip, referring to the one-to-many or fan-out nature of this pattern (more about this later). Both implementations involve a remote object, zero or more proxies to other replicas, and a vector of contained attributes. Fortunately, through the use of the interface, the difference is completely invisible to the application software on either side.

The Subject interface also derives from the Watchable interface, basically adding the functional equivalent of java.util.Observable. Any object, whether in the client or the server, can implement the simple Watcher interface and subscribe to a subject to receive update messages.

The only way to create a new subject is through the use of the SubjectFactory class, which fabricates a fully connected client-side replica of its server-side counterpart. The bidirectional connection mechanism internal to the replicas is hidden from view by this factory process.

public class SubjectFactory {
   ...
   public Subject createSubject(String theName)
   throws RemoteException
   ...
}

The SubjectFactory above makes use of a String to make a selection out of the group of available subjects, but various different means of selection can be imagined.

Keep in mind what must happen during subject creation on the client side. Not only must a two-way, yin-yang pair of interpenetrating remote-object-and-proxy pairs be created, but the entire contents of the server-side subject must be transported to the client to start the replica off properly. From that point onward, only the changed parts come over the line.

Hubs and tips

The remote objects on the server and client sides are fundamentally different from each other: The server-side subject needs to maintain contact with a number of client-side representatives and propagate updates to them. Remember, the two participants are the hub and the tip. The hub lives on the server, and the tip lives on the client.

The Hub interface is really of no interest to the application programmer -- it only represents exactly how the client-side tip sees its hub. Hub is a typical RMI remote interface, and it closely resembles the Subject interface itself. The difference is that when a tip talks to its hub, the tip must identify itself.

interface Hub extends Remote {
    void setRemote(Object theObject, int theIndex, int theTipNumber)
    throws RemoteException;
    void unlink(int theTipNumber) throws RemoteException;
}

The way the tip looks to the hub is a little simpler than the way the hub looks to the tip, with the setRemote() method basically mimicking the set() method of the Subject class. The only difference is that Subject.set() is called by the application code, while the Tip.setRemote() is a remote call originating on the server.

interface Tip extends Remote {
   void setRemote(Object theObject, int theIndex)
   throws RemoteException;
   void unlink() throws RemoteException;
}

Both the Hub and Tip interfaces have unlink() methods, giving them the opportunity to break their partnership; these methods will be called when somebody uses the subject's dispose() method.

Another factory

When a new connection is to be built between a new tip and a possibly-existing hub, a request must be sent to the server to create (or find, depending on the application) a hub counterpart for the tip being created on the client side.

The factory design pattern is an ideal way to code this sort of process, since it presents a way to hide the mechanics of instantiation. In this case, it also hides the tricky side-effect of setting up a bidirectional connection. This request is sent from the client-side SubjectFactory to the server-side HubFactory, which is an RMI remote object. It's the only remote object that must be found using the Naming.lookup() method.

The HubFactory has only one method, createHub, which receives the selection string and the tip's proxy. It returns a HubLink which is a special serializable object encapsulating the link to the server used by the client-side tip.

public interface HubFactory extends Remote {
   public HubLink createHub(String theName, Tip theTip)
   throws RemoteException;
}

A handy decorator

When a tip communicates with its hub counterpart, it must distinguish itself from the other tips so that the hub is able to broadcast the change message to everyone but the sender. However, since the application programmer sees only subjects (things that implement the Subject interface), there is no need for the tip itself to know which of the hub's tips it represents. Knowledge of the different tip identities (with respect to their hub) is encapsulated in the HubLink class below. It is a decorator in the sense that it dresses up the set() call by adding tip identity to the call.

class HubLink implements Serializable {
   ...
   private  Hub   hub        = null;
   private  int   tipNumber  = -1;
   ...
   public void set(Object theObject, int theIndex)
   throws RemoteException {
      if ( hub != null ) {
         hub.setRemote(theObject,theIndex,tipNumber);
      }
   }
   ...
   public void dispose()
   throws RemoteException {
      if ( hub != null ) {
         hub.unlink(tipNumber);
      }
   }
   ...
}
Related:
1 2 Page 1