ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Developing Highly Distributed Applications with Jtrix

by Nik Silver
05/08/2002

Jtrix is an open source Java platform for creating highly scalable, distributed, and efficient Web services. This article describes Jtrix, compares it to other Java technologies, and illustrates how to write a Jtrix application -- both a client, and the service it uses. You can download Jtrix from the Jtrix.org Web site or look it up on Sourceforge.

What is Jtrix?

How can you write an application that is economical to run? To answer this question, you need several things. You cannot create everything yourself, so you'll need to outsource some services: database, server farms, DNS server, credit card authorization, etc. And of course, you should expect to pay for these services, so you and your service providers need to measure usage accurately. If your application is successful, you'll need to allocate more processing power and more server space in various parts of the world. And you shouldn't have to rewrite your application to cope with that.

Jtrix addresses these issues. It's about creating and using Web services of any kind, and making applications distributed for reliability and mobile so they can move to new locations. Those roaming Jtrix programs are called netlets, and many netlets will work together to provide a highly scaled, distributed service or application. Each netlet will run on a node -- a Jtrix runtime environment.

But Jtrix is also about making applications sensitive to resource usage and need. Applications should be able to grow and shrink as needed, so they don't cost a fortune to run before demand increases, or when it shrinks. In the Jtrix world, the same application will cost 1,000 times less to run when there are 1,000 times fewer users. If you ran a dot-com a few years ago, you'll appreciate the business need for such an architecture.

How Does Jtrix Compare to ...

Jtrix has been inspired by many technologies, most of them Java-based. As befits a highly distributed system, Jtrix is very peer to peer. But whereas JXTA is a P2P communications system, Jtrix doesn't care how its communication works. A Jtrix application can use JXTA if it likes, or it can use something else. We're actually considering using JXTA in a couple of places; if it had been released five months earlier, we might be using it already.


O'Reilly Emerging Technologies Conference

The 2002 O'Reilly Emerging Technologies Conference explored how P2P and Web services are coming together in a new Internet operating system.


Another P2P touchstone is Gnutella. In fact, Gnutella would be a good application for Jtrix, but this is only one possibility; Jtrix is far more general and is intended for all kinds of Web services.

Jtrix's concept of accessing services is very close to Jini, because when one netlet uses a service, it's provided by a second, downloaded, netlet. That second netlet may be a simple proxy or carry a lot of intelligence. But Jtrix is very strong on security, so neither netlet can trust the other enough to let it run in its own class space. The node ensures the two are separated from each other and itself. So Jini is suited to a trusted Intranet environment, while Jtrix is designed for the hostile world of the Internet.

Enterprise Java Beans are all about distributed systems, too. The EJB concept of an application is very much the client/server, three-tier architecture, whereas Jtrix applications are much more P2P and don't enforce any heirarchy among netlets. Also, EJB distributes objects, which again download into the client's class space; Jtrix netlets are separated, as we've already said, and they are more about delivering full services, not just single objects.

Actually, since EJB servers are much higher level than Jtrix, an EJB server would be an excellent application to implement in Jtrix. It would produce a very highly scalable and mobile application server with all the high-level functionality of EJBs.

Jtrix Jargon

We've already met netlet and nodes. A netlet must implement the INetlet interface. It is described by an XML netlet descriptor, which tells the node how to get the netlet's code, load it and initialize it.

We also know that any Jtrix application can provide or use a service. A service connection is a two-way deal, and if your netlet uses a service, then it must offer one in return, even if it has no function. The class NullService provides a do-nothing service for this purpose.

A service is accessed by means of a warrant, which is an XML document. The warrant will have been issued by the service provider, probably to you personally, so that when you use it they recognize you. Then they can give you access to your "account" (your files, in the example of a data storage service), and bill you if needed.

To connect to a service, we say you bind to it. The node handles all connection details. Once you've bound to a service, you can access one of its facets, which is just any Java interface it offers. Getting a facet is also called binding.

Any of these facet interfaces will implement the IRemote interface, in much the same way that RMI has its Remote interface. In Jtrix, all interfaces start with the letter I, just so we know what we're dealing with.

Hello, Jtrix World

Here's a "Hello, World" program in Jtrix. We're first writing a client netlet which uses a Jtrix "Hello, World" service; we'll write the service itself later. The client takes a warrant, asks the node to bind the corresponding service, binds the facet offered by the service (IHelloFacet), and then gets the message contained within. This code and more is discussed in the complete programmer's guide, "How to Write Netlets." All of the Jtrix JARs and tools needed are in the download bundle on the site.

First, here's the definition of the IHelloFacet:


package org.jtrix.project.helloworld;

import org.jtrix.base.*;

public interface IHelloFacet extends IRemote
{
    public String getMessage();
}

Now here's the netlet. Most of it is made up of trivial do-nothing implementations of methods in the INetlet interface. The initialise() method does all of the real work. (Note the British spelling!) It binds to the service, gets the facet, and uses it. It offers the NullService mentioned above as its side of the two-way service connection.


package org.jtrix.project.helloworld;

import org.jtrix.base.*;
import org.jtrix.project.libjtrix.netlet.NullService;

/** Netlet which accesses a hello world service using a warrant
 * read from an incoming parameter.
 */
public class Hello1Client implements INetlet
{
    public byte[] initialise(INode node, Object param, byte[] unsigned)
        throws InitialiseException
    {
        try
        {
            // Get the warrant, use it bind the service, and then get the facet
            Warrant warrant = (Warrant)param;
            IService service = node.bindService(warrant, new NullService());
            String hf_name = IHelloFacet.class.getName();
            IHelloFacet facet = (IHelloFacet) service.bindFacet(hf_name);

            // Use the facet
            System.out.println(facet.getMessage());
            return null;
        }
        catch (Throwable e)
        {
            System.out.println("Sorry, no message available");
            e.printStackTrace();
            throw new InitialiseException(e.toString());
        }
    }

    public void terminate(long date, IShutdownProgress progress)
    {
        // Nothing to clean up when we terminate
    }

    public IService bindService(Warrant warrant, IService consumer)
        throws ServiceBindException
    {
        // This netlet offers no services to other netlets using warrants
        throw new ServiceBindException();
    }

    public String[] getFacets()
    {
        // This netlet offers no facets to the node
        return new String[0];
    }

    public IRemote bindFacet(String facet) throws FacetBindException
    {
        // The node shouldn't try to bind facets from here
        throw new FacetBindException();
    }
} // Hello1Client

To compile this, you need jtrix.jar and libjtrix.jar on your classpath, and you can put both classes into hello1.jar.

However, this client netlet is useless without a "Hello, World" service. So let's write that next.

What is a Jtrix Service?

To recap, a Jtrix service is anything that offers features and functionality to a netlet any time, anywhere. We'll get to the "any time, anywhere" part in a moment, but first, here are some examples of Jtrix services:

In fact, all of these services have already been created, with the servlet service being a Jtrix wrapper around the Tomcat servlet engine. You can download them from the site.

The "any time, anywhere" part of a service is a central principle of Jtrix. Jtrix aims to provide services that are always available to anyone who has the rights to access them. It recognizes the fact that servers are never 100% reliable, so Jtrix services tend to be distributed and redundant.

What Makes Up a Service?

There are really only two necessary parts to a Jtrix service. The first is a warrant -- an XML document that allows the holder access to a service. The client netlet presents the warrant to its node, which then puts it in touch with the service.

When the node is given a warrant it downloads a new netlet, the access point netlet -- the second required part of a Jtrix service. The client netlet talks to the access point netlet, which then fulfills the service, most likely by proxying requests back to a central server.

An access point netlet is a bit like an RMI stub, but there are key differences. For one thing, the access point netlet represents the entire service, not a single object. For another, it never runs in the same class space as the client code, thus ensuring security and version safety.

We might think that a server would be an essential part of a Jtrix service, but this is not so. An access point netlet doesn't have to proxy requests back to a server; it could deal with them itself.

Possibilities For a "Hello, World" Service

Now we know what a service consists of, here are some ideas of how we might construct a "Hello, World" service:

For the sake of simplicity, we'll implement the last option only.

Writing a Service

To implement our access point netlet, we first need to have the facet that provides the message. That's IHelloFacet, shown above.

Then the code of our access point netlet implements the INetlet interface, just like the client. Here's the code, which we'll discuss later:


package org.jtrix.project.helloworld;

import org.jtrix.base.*;

/** Netlet which provides a Hello World service in a very simple way.
 */
public class HelloServer implements INetlet
{
    private static final String _facet_name = IHelloFacet.class.getName();
    private INode _node;

    public byte[] initialise(INode node, Object param, byte[] unsigned)
        throws InitialiseException
    {
        _node = node;
        System.out.println("Server started");
        return null;
    }

    public void terminate(long date, INetlet.IShutdownProgress progress)
    {
        // Nothing to clean up when we terminate
    }

    /** Provide the service requested by the client.
     * @param warrant   The warrant which the client used for binding
     * @param consumer  The client's reciprocating service interface.
     * @return  A service connection.
     */
    public IService bindService(Warrant warrant, IService consumer)
        throws ServiceBindException
    {
        return new HelloService();
    }

    public String[] getFacets()
    {
        // We offer only the IHelloFacet
        return new String[]{ _facet_name };
    }

    public IRemote bindFacet(String facet) throws FacetBindException
    {
        // The node shouldn't try to bind facets from here
        throw new FacetBindException();
    }

    /** A simple implementation of IHelloFacet
     */
    private class HelloFacet implements IHelloFacet
    {
        public String getMessage() 
        {
            return "Hello, world";
        }
    }

    /** This is the real implementation of the hello world service.  It's
     * handed out only through the bindService() method of the public
     * (netlet) class.
     */
    private class HelloService implements IService
    {
        /** Respond to the service connection failing. If it does fail
         * this netlet is useless, so we terminate ourselves.
         */
        public void terminate()
        {
            System.out.println("Service terminated, terminate ourselves");
            _node.requestTermination();
        }

        /** See what facets this service offers the binding (consumer) netlet.
         */
        public String[] getFacets()
        {
            return new String[] { _facet_name };
        }

        /** Allows the consumer netlet to bind a specific facet.
         * @param facet  The name of the facet to bind.
         * @return  An interface to the requested facet.
         */
        public IRemote bindFacet(String facet) throws FacetBindException
        {
            System.out.println("Instantiating "+facet);

            if(!facet.equals(_facet_name))
            {
                throw new FacetBindException();
            }

            System.out.println("Facet instantiated");
            return new FacetHandle(new HelloFacet(), facet);
        }

    } // HelloService

} // HelloServer

The first few methods are how this netlet communicates with the node. It doesn't do much, but initialization does save a reference to the node for the future.

We can see that the service comes out of the bindService() method, which creates a new HelloService object, defined further on. Notice that it provides one facet to the client netlet, which is called by the bindFacet() method.

This code is discussed in much more detail in the document "How to Write Netlets." You should be see, however, that writing a service in Jtrix is not necessarily a difficult thing, at least when the service is simple. Also, the service binding is a two-way connection (each provides an IService to the other), which can be useful if the client needs to offer facilities to the service.

Preparing the Example

To compile the code, we need jtrix.jar on our classpath, which contains all of org.jtrix.base. Then we can bundle all of the classes into a single JAR, which we'll call helloserver.jar. Make sure the JAR containsthe following: HelloServer.class, HelloServer$HelloFacet.class, HelloServer$HelloService.class, and IHelloFacet.class.

Next, we need to create a warrant, which every service needs. A warrant tells the node how to access the service by telling it about the access point netlet. But first we need to create a netlet descriptor, which describes an access point netlet. Our current directory contains the hello1.jar file from our client code:


% ls
hello1.jar  helloserver.jar
% jtrixmaker -type netlet -outfile hello-server.xml \
    -jardirs . -jars helloserver.jar \
    -classname org.jtrix.project.helloworld.HelloServer
% ls
hello1.jar  helloserver.jar  hello-server.xml
%

This gives us the netlet descriptor for the access point netlet, hello-server.xml. And now we can put this into a warrant for the service:


% jtrixmaker -type warrant -descriptor-in hello-server.xml \
    -outfile hello-local-warrant.xml
% ls
hello1.jar  hello-local-warrant.xml  helloserver.jar  hello-server.xml
%

This warrant gives access to our service to any netlet that holds it. If you examine the warrant, you'll see it's very large because it actually contains all of the necessary JARs. In more realistic examples, you can upload the JARs to a Web server and supply jtrixmaker with their URLs. This makes warrants much easier to manage.

Now let's go back to our client netlet. That took a warrant as a parameter and used it to connect to the service. Therefore we need to create a descriptor for the client netlet and include this warrant as a parameter. Here's how we do it, putting it in the file hello1-client.xml:


% ls
hello1.jar  hello-local-warrant.xml  helloserver.jar  hello-server.xml
% jtrixmaker -type netlet -outfile hello1-client.xml \
    -jardirs /home/nik/jtrix/bin . -jars libjtrix.jar hello1.jar \
    -classname org.jtrix.project.helloworld.Hello1Client \
    -param {warrant:hello-local-warrant.xml}
% ls
hello1-client.xml  hello-local-warrant.xml  hello-server.xml
hello1.jar         helloserver.jar
%

Running the Demo

Finally we start up a new node. Here's the command we use and the output produced:


% jnode 211 -netlet-stdio hello1-client.xml
Jnode starting...
Initialising Nodality...
Bootstrap starting
starting hello1-client
netlet:211.0.2: Server started

Bootstrap complete 
netlet:211.0.2: Instantiating org.jtrix.project.helloworld.IHelloFacet
netlet:211.0.2: Facet instantiated
netlet:211.0.1: Hello, world
^C

%

We hit Control-C to stop the node, which would otherwise carry on waiting for more service requests. The jnode command starts a Jtrix node, and the integer that follows is just some arbitrary identifier. This helps when we run node clusters. The -netlet-stdioM option says that standard I/O from the netlets should be output, which is useful for debugging. Finally we name the netlet descriptor.

The output is from three sources, which don't appear in sync. We get the node's output ("Bootstrap starting," etc.), the service netlet's output (labelled 211.0.2) and the client netlet's output (labelled 211.0.1). This shows that the client netlet triggers the service and can then output the message. This is a complete Jtrix client and service!

Where to From Here?

We've seen just a little of what goes into a Jtrix client and service. There is much more we could talk about, such as proxying and self-redundancy. Jtrix doesn't impose these things on developers, nor does it even impose communication standards. It allows an environment for secure distributed systems, and complete freedom: we can use anything from SOAP to much lighter-weight protocols. It's up to the application.

A self-redundant multi-server version of the "Hello, World" service has been produced. It was written using a Jtrix framework called Beatrix, which has also allowed us to write the HTTP server, servlet engine, and DNS that we mentioned originally. The self-redundant multi-server "Hello, World" service required us to write only seven classes within Beatrix.

Conclusion

This introduction has shown how to write a Jtrix client and server, albeit briefly. For much more detail on Jtrix, see http://www.jtrix.org and in particular the document "How to Write Netlets," from which this information is taken. I hope this brief glimpse whets your appetite for developing high-availability, distributed systems with Jtrix.

Nik Silver is manages the open source Jtrix project.


Return to Onjava.com.

Copyright © 2009 O'Reilly Media, Inc.