|By Jeff Nelson||
|August 1, 1997 12:00 AM EDT||
Distributed object solutions to date have relied on pass by reference for method arguments. However, pass by value results in a much more scalable solution in some applications which manipulate numerous, fine-grained objects. This article examines how to pass by value with CORBA. Then, this implementation is used to extend the RMI on CORBA design presented in the previous issue.
Distributed object technologies, such as CORBA, allow developers to write applications in which objects in one program invoke methods on objects in another program. Thus, this greatly simplifies the task of writing client/server software.
However, argument passing for complex types, such as objects, doesn't work in quite the same way as for the primitive types and structures. At present, objects are always passed by reference in CORBA. For example, if you were implementing a BankTeller system and the client instantiated an instance of a Customer object, the client could pass a reference to its instance of the Customer object. The BankTeller could then manipulate the Customer object, invoking its methods and accessing its member variables using this reference. The instance would always be confined to the original CPU where it was instantiated, though. Other systems could manipulate the object through the reference, but not own their own copies of the object.
Passing objects by reference permits many different clients to manipulate large, complex objects in an efficient way. Moving instances of objects representing large data repositories or nested business objects would quickly grind a network to a halt. By passing only a reference to an object, which in CORBA is represented by a string of text approximately 200 bytes long, large objects can be used in a much more scalable manner.
On the other hand, passing objects by value has benefits as well. For example, some objects, called transient objects, are only accessed by one client. In such a situation, keeping the object on the server across a WAN may not make sense in comparison to sending the instance of the object directly to the client. Also, some objects are very small, perhaps only a few bytes in size. If such objects are actually smaller than the roughly 200 byte size of the network representation of a CORBA object reference, then a performance gain may be realized by actually copying the value of the object rather than using an object reference. Finally, local instances of objects may be manipulated more efficiently than objects that are remote just because of network overhead.
These motivating forces lead to the Objects-by-value service in CORBA, which is expected to become part of the CORBA standard by the time this article is published. Objects-by-value allows method invocations to occur in which the objects are passed by value rather than by reference. Visigenic's Caffeine is an example of a CORBA distributed object development tool with support for the new Objects-by-value service.
Roll Your Own
While many will opt to purchase Caffeine, writing pass by value for yourself is not difficult. In the last column I examined how JacORB, a free CORBA development tool, could be used to implement a simple RMI clone with CORBA compatibility. Now, let's look at how JacORB could be used to implement passing objects by value. The benefit of using JacORB rather than a commercial product is that all the source code is available for you, the reader, to try out for yourself. Just download it from my Web site mentioned at the end of this article.
Let's consider an example of a problem in which pass by value may be useful. Suppose you were asked to implement a distributed point of sale system for a grocery store. One obvious object that your solution would almost certainly include is an Item object representing an individual grocery item available for purchase. The grocery item might be as simple as an object with a few members, such as the name and cost of the item. A sample of an implementation of the Item object is shown in Listing 1.
The grocery store system might also include a Bag object representing the grocery bag of each store customer. The Bag object would have a method such as getItem() for obtaining the next grocery item, represented by an Item object, in the customer's grocery bag. For the purposes of this article, let's say the Bag object is limited to just that much capability and is represented by the following interface.
public interface Bag
public Item getItem();
} Consider this problem for a moment in the context of argument passing. The Item objects are small and numerous. Each client is likely to have many of them, potentially hundreds. If pass by reference were used, all of the instances of these hundreds of Item objects for every client would have to live on the server. Clearly, this could introduce problems with scalability since many clients communicating with one server might easily instantiate tens of thousands of small Item objects. This problem is clearly a candidate for pass by value in order to optimize performance and increase scalability. Let's tackle it by building pass by value using JacORB.
JacORB and Caffeine are among a handful of tools which supports the compiling of Java interfaces into distributed application stubs and skeletons. Typically, a CORBA developer would have to write Interface Definition Language (IDL), but this requirement is eliminated when using tools such as JacORB. However, unlike Caffeine, JacORB does have one limitation. It does not allow instances of Java objects to be passed in methods. For example, in the Bag interface, getItem() returns an Item object which is an instance of a Java class. Without the Objects-by-value service, which is implemented in Caffeine but not in JacORB, an instance of the Item object cannot be returned.
As a first step, then, let's wrap the instance of the Item object within a remote proxy which is a legal CORBA interface. Let's name this proxy ItemProxy and give it a few accessors for its member variables. The resulting ItemProxy is shown in Listing 2. In practice, you might envision a compiler generating all of this code based on the results of parsing the Item interface.
The Bag object must have a proxy of its own since returning an instance of Item makes the original Bag interface illegal for CORBA without Objects By Value. The BagProxy interface is identical to the original Bag interface but returns an ItemProxy object reference from getItem() rather than an instance of Item. By the way, the Proxy design pattern was documented very well by Hans Rohnert in "The Proxy Design Pattern Revisited". We also need to implement these interfaces with classes that wrap the corresponding instances of Item and Bag. For example, BagProxyImpl would contain members such as those shown in Listing 3.
Implementing ItemProxy requires a little bit more than simply wrapping an instance of Item. In this case, the Item object must have the added capability of moving from place to place, so some capability must be provided for externalizing the Item object, copying it across the network and internalizing it on the other side. The CORBA Externalization service could be used to implement this. In fact, the architecture of CORBA Externalization is very similar to Java Object Serialization. However, for simplicity, I prefer to just copy the Item object into a byte array and pass the byte array. The implementation of this is available with the downloadable source code. Due to space restrictions, it could not be reproduced here.
At this point, we have passed all the real road blocks for writing our own pass by value. The only remaining question is how to list the Bag instance with a name server for the world to see. Since the Bag object is not a legal CORBA interface, the Bag is, of course, simply wrapped in the BagProxy interface by instantiating a BagProxyImpl. On the client side, the BagProxy is replaced by a stub implementing Bag using dynamic stub loading. This technique is really very simple despite its grandiose name. All this means is that on the client side an object implementing the Bag interfaces is constructed using the BagProxy and returned from the Naming service. The full details are available in the downloadable source code.
That's it! We are ready to write a client and server that uses pass by value. Our sample server client is very simple - just two lines of code that instantiate an implementation of Bag and list it with the Naming service.
BagImpl obj = new BagImpl("BagServer"); Naming.rebind("BagServer", obj); The sample client is equally simple - three lines of code.
Bag obj = (Bag) Naming.lookup("http://localhost/BagServer"); Item anItem = obj.getItem(); System.out.println(anItem.name + "costs" + anItem.cost + "."); Notice that in this sample client, values of the instance of the Item object are accessed directly. In other words, this parameter was passed by value!
The results of running this sample are just the following:
Pound of Coffee costs 4.25.
Nothing spectacular, unless you purchase a lot of coffee, but it demonstrates that we have successfully passed an object by value. The design of the grocery store distributed applications can now take advantage of this capability to construct a more efficient, scalable system.
One of the nice features of this implementation of passing objects by value is that the Proxy interfaces can be reused even by CORBA products which don't support pass by value. This is a significant gain since it allows existing Visual Basic, Ada and C++ tools to easily work with the new system. For example, with only a little extra work, we could also write a Visual Basic client for the grocery store example.
Pass By Value In Other Orbs
Some other non-CORBA compliant Object Request Brokers also support passing objects by value. For example, in Remote Method Invocation (RMI), objects are categorized as either local or remote at the time the object is implemented. This is done by implementing the Java interface java.rmi.Remote to indicate that an object is remote. All other objects are treated as local objects. When determining what argument passing policy to use, RMI depends on these categories. Remote objects are passed by reference, while local objects are passed by value.
This design has several implications. First, local objects, such as a Java Vector, cannot be passed by reference. Java developers, who are familiar with passing Vectors into methods, having the Vector modify the method and then looking at the results when the method returns, may be concerned about this.
Second, many applications have objects which should be treated as local or remote depending on the context in which they are used. Since the determination of whether an object is local or remote with RMI is decided at the time the object is implemented rather than at the time the object is used, these applications will actually require the maintenance of two independent object hierarchies. This could mean the need to implement two complete copies of all the objects in the entire object model, doubling the resource requirements of a project.
Finally, RMI's pass by value is based on Java Object Serialization, which is not well specified. This feature more than anything else stands in the way of interoperating the existing Java RMI with other programming languages, such as C++ and Visual Basic.
In last month's column, I examined how to build many of the features of RMI on top of CORBA, resulting in allowing RMI to interoperate with other CORBA tools and many different programming languages. In fact, the column showed a running example of an RMI application working with a Visual Basic client.
Despite these concerns about pass by value in RMI, this design of RMI on CORBA can now be extended to included pass by value. However, one firm requirement is that this implementation should not modify the way in which RMI developers write their applications, even how they use pass by value.
Let's go back to the grocery store example and use that as a test candidate to implement RMI's pass by value with CORBA. As a first step, let's write a pure RMI version of this example. The source code required to do this is virtually unchanged. Of course, the "generated" files such as all the Proxy and ProxyImpl files are not required by RMI. Instead, RMI will provide its own generated files called "BagImpl_Stub.java" and "BagImpl_ Skel.java". The Naming service implemented above is actually API compatible with RMI's Naming service. All you have to do is add the appropriate "import" statements at the top of the source code to include the Java RMI packages. Then, compile the example using the "rmic" compiler.
When you run the RMI example, the client should print out:
Pound of Coffee costs 4.25.
How do we implement this pass by value example on CORBA? Well, first just start with the same source code presented last month which implements RMI constructs such as Remote, RemoteException and Naming on top of CORBA. The only required addition is support for the new Proxy interfaces presented above.
Incredibly, all of these changes are restricted to the introduction of a new set of stubs and some slight changes to the Naming object. The Naming object is responsible for locating an object that implements the Bag interface. The dynamic stub loading capability of the Naming object will handle the construction of a Bag stub based on the BagProxy which was passed to it. In order to handle pass by value, the Item stub includes a fromByteArray() method which internalizes an instance of Item. That's it!
Once these changes are made, you should be able to run the actual RMI example (shown just a moment ago) right on top of CORBA, even though it includes pass by value. One rather nice benefit of this new design of pass by value for RMI is that it is completely interoperable with our implementation of pass by value in CORBA as well as with older CORBA products which don't support pass by value. For example, if you took the time to write a Visual Basic client for the BagProxy interface, at this point you could go ahead and run this client with your RMI server!
Pass by value is a handy tool for implementing distributed applications. The CORBA Objects-by-value service is an example of pass by value in CORBA tools. I have demonstrated a rather simple example of how to implement pass by value using byte arrays and proxy interfaces. One of the benefits of proxy interfaces is that they allow excellent compatibility with CORBA tools which do not support the Objects-by-value service. Finally, CORBA Objects-by-value can be used to implement pass by value for other ORBs such as RMI, resulting in a high degree of interoperability between the two technologies.
Where To Go From Here
JacORB is available on the Internet at http://www.inf.fu-berlin.de/~brose/jacorb/ . RMI and Java can be found at http://www.javasoft.com. CORBA standards, such as Objects-by-value, can be found at http://www.omg.org. Visigenic, the maker of VisiBroker for Java and Caffeine, can be found at http://www.visigenic.com