Two objects on every table

21 May 1997

"Object-oriented" is well on its way to becoming part of that elite group of phrases that make you want to depart from a conversation1 as soon as possible, no matter how attractive the participants. What it is that drives a phrase into ubiquitious buzzword-dom I cannot say, but if you could bottle it and sell it, Steve Jobs would have picked up a truckload about five years ago and started shoving the topic of this article down everyone's throat. Enough with obscure references; what I'm talking about here are distributed objects. In a nutshell, a distributed object is like a regular object2,



I'll limit my endeavors this week to creating a distributed-object system with the ability to do something actually exciting in next week's article.


only it is shared between processes on a machine or processes on different machines3. Like object-oriented programming, this relatively simple concept can prove to be a very useful way to organize one's applications.

Deep objects and distributed magic
What better way to introduce the topic of distributed objects than to implement a distributed-object system4? Since I don't exactly have time to write a full-fledged, general-purpose distributed-object system -- and I certainly don't have time to explain one in detail -- I'll limit my endeavors this week to creating a distributed-object system with the ability to do something actually exciting5 in next week's article. The architecture of this system is relatively simple. A server runs on a machine (the one that served you this article, in fact) and manages all the objects. Clients connect to that machine and register as subscribers to those objects. Whenever a change is made to an object (by any of the clients or by code running as part of the server), an attribute-changed event is broadcast to all the subscribers of that object. The clients automatically update their local representations of the object and broadcast the attribute-changed event to any local subscribers.

Let's take a look at some excerpts of the code for a clearer understanding of what all this means:

public class DObject
{
    public int getValue (String name, int def);
    public void setValue (String name, int value)
        throws IOException;

    public String getValue (String name, String def);
    public void setValue (String name, String value)
        throws IOException;

   // getting and setting arrays of int and arrays
   // of String is also available
}
Since we don't yet have support for Java Object Reflection the DObject has to provide specialized member-functions for getting and setting values. When a value is set, it automatically informs its local object manager (described below), who takes care of sending attribute-changed events to the appropriate parties.
public class Event
{
    public final static byte OBJECT_CREATED = 0;
    public final static byte OBJECT_DELETED = 1;
    public final static byte ATTR_CHANGED = 2;

    public byte type;
    public String oid;
    public String name;
    public Object value;
}

public interface Subscriber
{
    public boolean handleEvent (DObject target,
                                Event evt);
}

When a change occurs to a DObject attribute, an event is delivered to all subscribers of that object. In order to get a reference to a DObject, one must name a subscriber to be notified when changes occur to that object. The subscriber can respond accordingly to the attribute changes. This system is used by users of the distributed-object system as well as internally, to facilitate the distribution of attribute-changed events.

public interface DObjectManager
{
    public void attributeChanged (DObject object,
        String name, Object value)
        throws IOException;

    public DObject subscribeToObject (String oid,
        Subscriber sub) throws IOException;

    public void destroyObject (String oid)
        throws IOException;
    public void dispatchEvent (Event evt)
        throws IOException;
}

The last major piece of the puzzle is the object manager, which is where objects are obtained. It handles all the event-dispatch details. DObjects are obtained from the object manager and it is also used to destroy objects.

The hairy details
I'll try to quickly6 describe the mechanism by which attribute-changed events get properly passed to all subscribers of an object (both local and remote), but don't fret if this gets a little hairy. After all, it's just implementation detail.

There are two implementations of DObjectManager (notice that it is only an interface), one for the server and one for the client. All DObjects have a reference to the object manager. When the user calls any of the setValue (or related) member functions, the DObject notifies the DObjectManager that one of its attributes has been changed. The server object manager turns this notification into an attribute-changed event and broadcasts that event to all registered subscribers of that DObject.

If life were limited to the single-server process, this would suffice. However, a similar implementation in the client DObjectManager would not work. It would only broadcast attribute-changed events to local subscribers of a Dobject, not the potential multitude of other client-subscribers connected to the server. So every client maintains a proxy subscriber in the server process. When the client DObjectManager gets a subscribe request, it sends a message to its proxy in the server, asking it to subscribe to the object. The server proxy then marshals 7 the object and sends it down to the client. The client DObjectManager keeps that object around in a hash table for other potential local subscribers to use.

Now, whenever an attribute-change event happens at the server, it will be sent to this client's subscriber proxy, who will forward the event to the client. The client can then distribute the attribute-changed event to all local subscribers. Conversely, when the client DObjectManager


I'm going to implement a lame little chat system, then go back to doing real work.



receives an attribute-change notification from one of its local objects, it does not send it to the subscribers of that object, but forwards it to the subscriber proxy, who makes the same change to the server-object instance. This causes the server to generate an attribute-changed event and forward it to all of that object's subscribers (local and remote). As a consequence of that event, all the subscribers local to the client will eventually receive the attribute change, the same round-a-bout way that all the other clients received it. This also helps to equalize event-delivery timing.

If you want to know more, you'll have to take a look at the source.

The token application
If you can remember all the way back to the beginning of the article, I mentioned something about using this to write something cool. Well, that's not going to happen now. I'm saving that for next week. So instead, I'm going to implement a lame little chat system, then go back to doing real work.

If any of this made sense, you might have a clue as to what the implementation is going to look like. I can sum it up for you in these two code fragments:

// send a chat message to everyone
String message = _name + ": " +  _text.getText();
_cobj.setValue("message", message);

// update our message display when a
// new message arrives
public boolean handleEvent (Event evt)
{
    if ((evt.type == Event.ATTR_CHANGED) &&
        evt.oid.equals("chat_object") &&
        evt.name.equals("message")) {
        _msgbuffer.appendText((String)evt.value+"\n");
    }
    return true;
}
That and a little user-interface code are all that are necessary to implement a chat system (a very simple and not terribly efficient one, but who's counting?)

Cheesy Chat

Bigger Cheesy Chat

Tune in next week for an interesting application of this nice little distributed-object system and an affirmation that other things besides chat are easy to do with distributed objects. *

-- Michael Bayne <mdb@go2net.com> is ogling the price of Solstice (tm) Enterprise Manager (tm) and wondering whether he should be distributing this source code.

Source code as a gzipped tar file or a zip file.