Achieving Synergy, ObjectSpace's Voyager ORB 3.0
- By Anez, Juancarlo
- December 18, 1999
|
|
|
|
Version Reviewed: 3.0
Current Version: 3.1.1
Cup rating system:
5 Outstanding
4 Very Good
3 Good
2 Acceptable
1 Poor
REVIEW IN A NUTHELL
Anyone developing distributed clients will want to check this out. It implements every
imaginable distributed computing feature with only a few abstractions and a small and
easy-to-use API. No other product on the market makes client programming easier.
Price:
Voyager Application Server = $1,999.00, Voyager ORB Professional = $799.00
Vendor Info
ObjectSpace Inc.
14850 Quorum Drive Suite 500
Dallas, TX 75240
v: 800.OBJECT.1 or 972.726.4100
f: 972.726.4200
[email protected]
www.objectspace.com
|
THE FIELD OF software development evolves so rapidly and changes so often that it's nearly
impossible to keep up. It's easy to feel bombarded by acronyms (CORBA, DCOM, JSP, ASP, EJB, UML)
and overwhelmed by (often competing) theories, standards, and technologies.
From time to time, a coalescing idea comes along that manages to tie it all together, to leverage
what exists and raise the level of abstraction a couple of notchesto find just the right
synergy. The invention of structured programming languages is an example of a synergistic breakthrough;
so are relational databases, graphical user interfaces (GUIs), Internetworking, and, more recently,
object-oriented (OO) programming and the Java platform.
I examined a product that, if not exactly the cause of a paradigm shift, is part of this kind of
synergistic breakthrough. With the Voyager ORB 3.0 from ObjectSpace, distributed computing has
never been easier, cheaper, or as widely available.
Voyager is a full-blown Java ORB architecture designed to support the development of powerful
distributed computing systems. The product uses the Java Virtual Machine (JVM) to load classes
at runtime to create mobile objects and autonomous agents. A complete and seamless distributed
computing framework, it supports remote invocation, remote pass by value, distributed events,
naming services, object mobility, autonomous agents, runtime object extensions, object activation,
distributed class and resource serving and loading, distributed security, distributed garbage
collection, distributed timers, advanced messaging, multicasting, and replaceable networking protocols.
The Voyager framework provides all of the above with only a few abstractions, a very small API, and
no need for interface definition languages (IDLs) or management of stubs or proxies. Voyager works
from interfaces defined in pure Java and automatically generates and distributes whatever stubs or
proxies it needs at runtime. All this functionality is provided by a single 500K JAR file of Java
that users can download free (for internal use) from the ObjectSpace Web site.
The Voyager ORB comes in two versions: the Voyager ORB Core, which is free for "internal" use (it
can't be distributed to third parties); and Voyager ORB Professional, the commercial version. Voyager
ORB Pro includes everything in the Voyager ORB, plus a configuration framework, JNDI integration,
CORBA naming service, and support for ultra-light (15K) clients. It also includes the Voyager Management
Console, a Java program that enables central configuration of services offered by different clients.
The just-released Voyager Pro version 3.1 (see sidebar) adds dynamic XML, connection management,
persistent replicated directory, directory load balancing, and support for DCOM.
ObjectSpace sells several products that work with Voyager to provide enhanced functionality, including
Voyager Transactions, Voyager Security, and the Voyager Application Server. These products provide
CORBA-compatible transaction support, advanced security, and an EJB-capable application server that
can be licensed separately.
OO for Everyone
Voyager truly is something of a breakthrough in distributed programming. It combines the various
features of Java to produce something greater than the parts. What Voyager does has never been done
before (in a commercially available product, anyway) and was almost impossible to do until the advent of Java.
The beauty of the Voyager ORB is the way it manages to hide the many complexities of distributed
programming by leveraging the strengths of the Java platform. Voyager uses introspection to discover
the features of whatever class users want to make distributable (at runtime). It then generates any
required wrapper classes on-the-fly by writing the bytecodes to memory and registering the just-created
classes with the JVM. Voyager lets users deploy code to a single location by leveraging Java's
class-loading mechanism; it supports flexible and custom security by embracing standard Java security;
it supports pass-by-value and object mobility by leveraging Java serialization. All this happens under
the hood, without any involvement required from the programmer. The program just makes a remote call
and classes get dispatched to the right place, connections are made, and objects are serialized and
reconstructed at the destination.
Dynamic Aggregation
At the core of Voyager's magic is its ability to attach secondary objects, or facets, to a
primary object at runtime. This is what the Voyager documentation calls Dynamic Aggregation.
Classes don't have to be modified in any way for instances to act as facets or to be extended by them.
IEmployee employee = new
Employee( "joe", "234-44-2678" );
IFacets facets = Facets.of( employee );
IAccount account = (
IAccount) facets.of(IAccount );
account.deposit( 2000 );
Classes used as facets do not need to be related in any way to their "primaries"; but Voyager allows
different implementations of a given facet to be attached to different primaries by using an algorithm
that searches for likely facet implementations using the primary's class name.
Those familiar with COM (the Microsoft Common Object Model) programming will probably recognize a
similarity between COM interface querying and facets. The difference is that Voyager's Dynamic Aggregation
allows for new facets to be attached to an object at runtime, even when the source code for the primary
is not available. Voyager's features such as Proxies, Mobility, and Facets are implemented using Dynamic
Aggregation.
Remote Invocation
In OO programming, remote invocation refers to the ability to invoke methods on remote objects (objects
that exist in a different machine) as if they were local. Remote invocation is usually implemented
through proxies, which provide a façade that hides the mess of the underlying plumbing. Voyager excels
in this regard in that this plumbing is automatically generated at runtime, for any class, as required.
This means that classes do not have to be modified in any way to be remote enabled. Most notably, they
don't have to descend from a framework class nor do they have to implement a specific interface.
Method calls made to a proxy are forwarded to its related object. If the object is remote, the arguments
are serialized using the standard Java serialization mechanism and reconstructed at the destination.
By default, parameters are passed by value, except for actual parameters that are already proxies,
or those with classes that implement
com.objectspace.voyager.IRemote or java.rmi.Remote;
these are passed by reference. The same rules are applied for method return values. Exceptions that
occur during a remote method call are caught at the remote site and rethrown locally.
Voyager also implements advanced messaging. A one-way message call returns immediately, discarding the
return value if any. For "future" messages, the call returns a proxy to the result, which can be polled
or read in a blocking fashion. Timeouts can be specified for future messages, and users can request
that a proxy is returned in place of the actual object:
try {
Object args = new Object[] {"XYZ"};
Result result =
Future.invoke(
market, "getStock", args, true);
result.setTimeout(5000);
IStock stock = (
IStock) result.readObject()
catch (TimeoutException e) {
//...
Voyager implements distributed security on top of standard Java security. This means that security
managers, permissions, signing, encryption, and custom security policies are all available to
distributed applications. It also means that it is not necessary to study the intricacies of yet
another security system to deploy secure distributed applications with Voyager. For additional
security, Voyager allows the classes that carry out communications between hosts to be replaced
with secure versions like SSL. Voyager Pro also provides a security implementation that's more
aware of distribution, and supports a central configuration and remote management.
Naming Services
The usual way of getting initial references to remote objects in distributed applications is to use
a naming service or registry. Voyager implements a distributed, hierarchical, and federated naming
service that is tightly integrated with the rest of the framework. References to named remote
objects are obtained passing the target host, port, and name to the Namespace.lookup() method:
IStockMarket market =
(IStockMarket) Namespace.lookup(
"//host:8000/NASDAQ");
Names can be bound to objects at creation time. The following call would bind the name NASDAQ to the
newly created object and export in the default (local) server:
IStockMarket market =
(IStockMarket) Factory.create(
"StockMarket", "NASDAQ");
Bindings can also be done when an object is exported via the Proxy.export()
method, or later, by using Name space.bind():
Proxy.of(market).export("NASDAQ");
Namespace.bind("NYSE", market2);
Hierarchical directories can be created by using slash-separated ("/") paths for the names:
Namespace.bind(
"services/exchanges/
NYSE", market2);
A distributed directory can be created by binding one directory to a name in a remote host:
Directory exchanges =
new Directory();
exchanges.put("NASDAQ", market1);
exchanges.put("NYSE", market2);
Namespace.bind(
"//remotehost:8000/
exchanges", exchanges);
The same API is used to access CORBA and JNDI directories just by using different URLs. For example:
IStockMarket market =
(IStockMarket) Namespace.lookup(
"rmi://host/NASDAQ");
A Voyager server also acts as a CORBA and JNDI naming server. Clients need only use the usual bootstrap
code for naming services.
Multicast Mechanism
Voyager implements an efficient multicast mechanism that supports the publish/subscribe metaphor. To
participate in multicast, a program places the objects that will be receiving the messages in a special
container called a Subspace. A Subspace can be connected to other local or remote subspaces to
form a Space. Messages sent to a Subspace are copied to neighboring Subspaces before being
delivered to objects in the local Subspace. The usual way to send a multicast message to objects
in a Space is to obtain a multicast proxy of the desired type. Method calls over the proxy translate
into multicast messages and eventually into calls of the same method over any object that implements
the same interface:
ISubspace subspace = (
ISubspace) Namespace.lookup(
"//localhost:8000/Subspace1" );
IBroker allBrokers = (
IBroker) subspace1.
getMulticastProxy( "IBroker" );
allBrokers.stockChanged("XYZ");
Voyager's publish/subscribe allows programs to register event handlers with a space, much like it's done
in Java's Abstract Windowing Toolkit (AWT). Programs subscribe to specific topics by adding instances of
the Subscriber class to a space. Topics are strings or names separated by periods,
like "stock.XYZ.up." A Subscriber
acts on messages that match the topic pattern passed to its constructor and forwards the received event
to its associated PublishedEventListener.
Object Mobility
Voyager allows remotely-enabled objects to be moved from host to host. Mobility allows moving objects that
exchange large numbers of messages closer to each other, reducing network traffic. Mobility can also be
used for server balancing and for clustering. Moving an object in Voyager is very simple:
IStockMarket market =
(IStockMarket)
Namespace.lookup("NASDAQ");
Mobility.of(market).moveTo(
"//another_server:7000");
Objects with classes that implement Voyager's IMobile interface will
receive pre- and post-notifications of departures and arrivals when they are moved.
Voyager provides a special type of mobility in the form of agents. Agents can roam around the network by
themselves carrying the necessary information to load their related classes and other resources. When an
agent is set to be autonomous, it won't be garbage collected even after there are no more references to it.
IBroker broker =
Factory.create("Broker");
IAgent agent = Agent.of(broker);
agent.setAutonomous(true);
agent.setResourceLoader(
new URLResourceLoader(
"http://thishost:8000"));
agent.moveTo("//markethost",
"roamAndBuy");
Existing proxies to objects that are moved are updated the next time they are used.
Resource Serving
Making sure that the Java class files for a distributed application are in sync on all the deployment
hosts can be a nightmare. Voyager solves this problem by allowing any HTTP (Web) server as a source for
class files and other resources:
VoyagerClassLoader.addURLResource(
"http://serverforclasses/path");
VoyagerClassLoader.
addURLResource(
"http://yetanotherserver");
Voyager itself can act as an HTTP server, so there's no need to deploy any other software for serving class
files. Voyager also allows for classes to be loaded from signed JAR files or from custom class loaders that
implement the IResourceLoader interface.
With resource serving, all the classes that implement a distributed application's functionality can be
deployed to a single host or a pool of them. Resource serving also allows for mobile agents to roam around
a network, arriving at hosts on which class files required for running the agent would normally not be
available.
Activation
Object activation means the ability to store objects to permanent storage and resurrect them afterward on
request, even when the underlying program has been restarted. Voyager's activation architecture is
independent of the persistence mechanism used. To enable activation for a given class of objects, a
program provides an activator and registers it with Voyager's activation manager.
ILibrary library = new Library();
LibraryActivator activator =
new LibraryActivator(library);
Activation.register( activator );
Namespace.bind( "Library", library );
An activator is an object whose class implements the IActivator
interface. It is responsible for providing a string that acts as a key and uniquely identifies activated
objects and for reconstructing the original objects when requested. Voyager provides support for taking
snapshots of activated objects and of their facets. After an object has been enabled for activation,
the process of activation is transparent to clients that hold references to activated objects.
Given the simplicity of Voyager programming, its CORBA features are especially appealing. I tested the
product's compatibility with the Visigenic ORB (VisiBroker) bundled with Inprise's JBuilder Enterprise
3. In the first test, a VisiBroker client tried to interact with the server program from Voyager's
simple "Bank" example. The second test used Voyager as a client against VisiBroker's own Bank example.
To perform the first test, I copied the files from Voyager's Bank example to a different directory.
Then IDL files were produced from the Java interfaces and exception classes using Voyager's CGEN utility.
The IDL files were then used to generate new Java interfaces and CORBA stubs and skeletons using
VisiBroker's IDL2JAVA tool. Then a client was written using the VisiBroker libraries. Both programs were
compiled, the server was started, and the client was run. This experiment didn't work. The VisiBroker
client halted with an exception no matter what I tried.
The second test applied the same process to VisiBroker's Bank example. This time the experiment ran on
the first try.
Conclusion
The only real complaint I have about Voyager ORB is the lack of documentation available about ORB semantics.
I've heard complaints from other users that the ORB has scalability problems due to the way it assigns
threads to requests, but my experience with the product revealed no such shortcoming. In any event, that
kind of problem is easy enough to foresee and simple enough to avoid with the right kind of documentation.
I strongly recommend this product to anyone developing distributed clients. No other product I'm familiar
with makes client programming easier. Developers working on distributed applications, especially those
involving applets and/or servlets, also would love this product. And anyone who wants to experiment and
learn about distributed computing could hardly go wrong downloading this product and taking it out for a
spin. Voyager ORB Core is free, after all. It's easy to install, simple to learn, and even simpler to use.
Juancarlo Añez is director of IT at Modelistica in Caracas, Venezuela. He can be contacted at
[email protected].
Building a Simple Distributed App
|
The following example is designed to give you a taste of the Voyager APIs and to demonstrate how
simple it can be to program distributed applications. Running Voyager is as simple as setting the
CLASSPATH and executing the distributed application. Voyager also includes a stand-alone server
that can be executed from the command line. These are the steps I went through to build a simple
distributed application that exercises object mobility:
- Define a Java interface that describes the objects we'll be moving around:
public interface ITalker {
void say(String message);
}
- Provide a straightforward implementation of the interface:
public class Talker
implements ITalker, java.io.Serializable {
public void say(String message) {
System.out.println(message);
}
}
The class must implement java.io. Serializable to be able to move around the 'Net.
- Compile the two Java files and start the Voyager ORB:
voyager 8000 -r
The number (800), tells Voyager which port receives requests. The "-r" parameter tells the ORB
to act as an HTTP server for classes in its CLASSPATH.
- Copy the ITalker.class to a different machine and write, compile, and run the following Java
program:
import com.objectspace.voyager.*;
import com.objectspace.voyager.
loader.*;
import com.objectspace.voyager.
mobility.*;
public class Houdini {
// change "momo" to the name
of the actual remote machine
static final String peer =
"//momo:8000";
public static void main(String[]
args) throws Exception {
// allow required classes to be
loaded from peer
VoyagerClassLoader.addURLResource(
"http:" +peer);
// start the local Voyager ORB
Voyager.startup();
try {
// create an instance of our
ITalker implementation
ITalker talker = (ITalker)
Factory.create("Talker");
// do the magic
talker.say("1,2...");
Mobility.of(talker).moveTo(peer);
talker.say("3! Applause
please!");
System.out.println("Applause
please!");
} finally {
Voyager.shutdown();
}
}
}
This small program started a client ORB, created an object from a class file provided by a different
machine, moved the object to another machine, and remotely invoked a method on the (now) remote object.
I used a text editor, a Java compiler, the Java Runtime Environment, and Voyager. There was no IDL,
and all the required proxies, stubs, and other plumbing were provided by Voyager at runtime by
leveraging the networking, serialization, and introspection capabilities of Java.
|