Distributed Object Architectures

EJB is a component model for component transaction monitors, which are based on distributed object technologies. Therefore, to understand EJB you need to understand how distributed objects work. Distributed object systems are the foundation for modern three-tier architectures. In a three-tier architecture, as shown in Figure 1-1, the presentation logic resides on the client (first tier), the business logic resides on the middle tier (second tier), and other resources, such as the database, reside on the backend (third tier).

Three-tier architecture

Figure 1-1. Three-tier architecture

All distributed object protocols are built on the same basic architecture, which is designed to make an object on one computer look like it’s residing on a different computer. Distributed object architectures are based on a network communication layer that is really very simple. Essentially, there are three parts to this architecture: the business object, the skeleton, and the stub.

The business object resides on the middle tier. It’s an instance of an object that models the state and business logic of some real-world concept, such as a person, order, or account. Every business object class has matching stub and skeleton classes built specifically for that type of business object. So, for example, a distributed business object called Person would have matching Person_Stub and Person_Skeleton classes. As shown in Figure 1-2, the business object and skeleton reside on the middle tier, and the stub resides on the client.

The stub and the skeleton are responsible for making the business object on the middle tier look as if it is running locally on the client machine. This is accomplished through some kind of remote method invocation ( RMI) protocol. An RMI protocol is used to communicate method invocations over a network. CORBA, Java RMI, and Microsoft .NET all use their own RMI protocols.[4] Every instance of the business object on the middle tier is wrapped by an instance of its matching skeleton class. The skeleton is set up on a port and IP address and listens for requests from the stub, which resides on the client machine and is connected via the network to the skeleton. The stub acts as the business object’s surrogate on the client and is responsible for communicating requests from the client to the business object through the skeleton. Figure 1-2 illustrates the process of communicating a method invocation from the client to the server object and back. The stub and the skeleton hide the communication specifics of the RMI protocol from the client and the implementation class, respectively.

RMI loop

Figure 1-2. RMI loop

The business object implements a public interface that declares its business methods. The stub implements the same interface as the business object, but the stub’s methods do not contain business logic. Instead, the business methods on the stub implement whatever networking operations are required to forward the request to the business object and receive the results. When a client invokes a business method on the stub, the request is communicated over the network by streaming the name of the method invoked, and the values passed in as parameters, to the skeleton. When the skeleton receives the incoming stream, it parses the stream to discover which method is requested, then invokes the corresponding business method on the business object. Any value that is returned from the method invoked on the business object is streamed back to the stub by the skeleton. The stub then returns the value to the client application as if it had processed the business logic locally.

Rolling Your Own Distributed Object

The best way to illustrate how distributed objects work is to show how you can implement a distributed object yourself, with your own distributed object protocol. This will give you some appreciation for what a true distributed object protocol like CORBA does. Actual distributed object systems such as DCOM, CORBA, and Java RMI are, however, much more complex and robust than the simple example we will develop here. The distributed object system we develop in this chapter is only illustrative; it is not a real technology, nor is it part of Enterprise JavaBeans. The purpose is to provide you with some understanding of how a more sophisticated distributed object system works.

Here’s a very simple distributed business object called PersonServer that implements the Person interface. The Person interface captures the concept of a person business object. It has two business methods: getAge() and getName() . In a real application, we would probably define many more behaviors for the Person business object, but two methods are enough for this example:

public interface Person {
    public int getAge() throws Throwable;
    public String getName() throws Throwable;
}

The implementation of this interface, PersonServer, doesn’t contain anything surprising. It defines the business logic and state for the Person:

public class PersonServer implements Person {
    int age;
    String name;

    public PersonServer(String name, int age){
        this.age = age;
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public String getName(){
        return name;
    }
}

Now we need some way to make the PersonServer available to a remote client. That’s the job of the Person_Skeleton and Person_Stub. The Person interface describes the concept of a person independent of implementation. Both the PersonServer and the Person_Stub implement the Person interface because they are both expected to support the concept of a person. The PersonServer implements the interface to provide the actual business logic and state; the Person_Stub implements the interface so that it can look like a Person business object on the client and relay requests back to the skeleton, which in turn sends them to the object itself. Here’s what the stub looks like:

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;

public class Person_Stub implements Person {
    Socket socket;
    
    public Person_Stub() throws Throwable {
        /* Create a network connection to the skeleton. 
            Use "localhost" or the IP Address of the skeleton 
            if it's on a different machine. */      
        socket = new Socket("localhost",9000);
    }
    public int getAge() throws Throwable {
        // When this method is invoked, stream the method name to the skeleton.
        ObjectOutputStream outStream = 
            new ObjectOutputStream(socket.getOutputStream());
        outStream.writeObject("age");
        outStream.flush();
        ObjectInputStream inStream = 
            new ObjectInputStream(socket.getInputStream());
        return inStream.readInt();
    }
    public String getName() throws Throwable {
        // When this method is invoked, stream the method name to the skeleton.
        ObjectOutputStream outStream = 
            new ObjectOutputStream(socket.getOutputStream());
        outStream.writeObject("name");
        outStream.flush();
        ObjectInputStream inStream = 
            new ObjectInputStream(socket.getInputStream());
        return (String)inStream.readObject();
    }
}

When a method is invoked on the Person_Stub, a String token is created and streamed to the skeleton. The token identifies the method that was invoked on the stub. The skeleton parses the method-identifying token, invokes the corresponding method on the business object, and streams back the result. When the stub reads the reply from the skeleton, it parses the value and returns it to the client. From the client’s perspective, the stub processes the request locally. Now let’s look at the skeleton:

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.net.ServerSocket;

public class Person_Skeleton extends Thread {
    PersonServer myServer;

    public Person_Skeleton(PersonServer server){
        // Get a reference to the business object that this skeleton wraps.
        this.myServer = server;
    }
    public void run(){
        try {
            // Create a server socket on port 9000.
            ServerSocket serverSocket = new ServerSocket(9000);
            // Wait for and obtain a socket connection from the stub.
            Socket socket = serverSocket.accept();
            while (socket != null){      
                // Create an input stream to receive requests from the stub.
                ObjectInputStream inStream = 
                    new ObjectInputStream(socket.getInputStream());
                // Read the next method request from the stub. Block until the 
                // request is sent.
                String method = (String)inStream.readObject();
                // Evaluate the type of method requested.
                if (method.equals("age")){
                    // Invoke the business method on the server object.
                    int age = myServer.getAge();
                    // Create an output stream to send return values back to
                    // the stub.
                    ObjectOutputStream outStream = 
                        new ObjectOutputStream(socket.getOutputStream());
                    // Send results back to the stub.
                    outStream.writeInt(age);
                    outStream.flush();
                } else if(method.equals("name")){
                    // Invoke the business method on the server object.
                    String name = myServer.getName();
                    // Create an output stream to send return values back to
                    // the stub.
                    ObjectOutputStream outStream = 
                        new ObjectOutputStream(socket.getOutputStream());
                    // Send results back to the stub.
                    outStream.writeObject(name);
                    outStream.flush();
                }
            }
        } catch(Throwable t) {t.printStackTrace();System.exit(0); }
    }
    public static void main(String args [] ){
        // Obtain a unique Person instance .
        PersonServer person = new PersonServer("Richard", 34);
        Person_Skeleton skel = new Person_Skeleton(person);
        skel.start();
    }
}

The Person_Skeleton routes requests received from the stub to the business object, PersonServer. Essentially, the Person_Skeleton spends all its time waiting for the stub to stream it a request. Once a request is received, it is parsed and delegated to the corresponding method on the PersonServer. The return value from the business object is then streamed back to the stub, which returns it as if it was processed locally.

Now that we’ve created all the machinery, let’s look at a simple client that makes use of the Person:

public class PersonClient {
    public static void main(String [] args){
        try {
            Person person = new Person_Stub();
            int age = person.getAge();
            String name = person.getName();
            System.out.println(name+" is "+age+" years old");
        } catch(Throwable t) {t.printStackTrace();}
    }
}

This client application shows how the stub is used on the client. Except for the instantiation of the Person_Stub at the beginning, the client is unaware that the Person business object is actually a network proxy to the real business object on the middle tier. In Figure 1-3, the RMI loop diagram is changed to represent the RMI process as applied to our code.

RMI loop with Person business object

Figure 1-3. RMI loop with Person business object

As you examine Figure 1-3, notice how the RMI loop was implemented by our distributed Person object. RMI is the basis of distributed object systems and is responsible for making distributed objects location transparent. Location transparency means that a server object’s actual location—usually on the middle tier—is unknown and unimportant to the client using it. In this example, the client could be located on the same machine or on a different machine very far away, but the client’s interaction with the business object is the same. One of the biggest benefits of distributed object systems is location transparency. Although transparency is beneficial, you cannot treat distributed objects as local objects in your design because of the performance differences. This book will provide you with good distributed object design strategies that take advantage of transparency while maximizing the distributed system’s performance.

When this book talks about the stub on the client, we will often refer to it as a remote reference to the business object. This allows us to talk more directly about the business object and its representation on the client.

Distributed object protocols such as CORBA, DCOM, and Java RMI provide much more infrastructure for distributed objects than the Person example. Most implementations of distributed object protocols provide utilities that automatically generate the appropriate stubs and skeletons for business objects. This eliminates custom development of these constructs and allows much more functionality to be included in the stub and skeleton.

Even with automatic generation of stubs and skeletons, the Person example hardly scratches the surface of a sophisticated distributed object protocol. Real-world protocols such as Java RMI and CORBA IIOP provide error- and exception-handling, parameter-passing, and other services such as the passing of transaction and security context. In addition, distributed object protocols support much more sophisticated mechanisms for connecting the stub to the skeleton. The direct stub-to-skeleton connection in the Person example is fairly primitive.

Real distributed object protocols, like CORBA, also provide an object request broker, which allows clients to locate and communicate with distributed objects across the network. ORBs are the communication backbone, the switchboard, for distributed objects. In addition to handling communications, ORBs generally use a naming system for locating objects and many other features such as reference passing, distributed garbage collection, and resource management. However, ORBs are limited to facilitating communication between clients and distributed business objects. While they may support services like transaction management and security, use of these services is not automatic. With ORBs, most of the responsibility for creating system-level functionality or incorporating services falls on the shoulders of the application developer.



[4] The acronym RMI isn’t specific to Java RMI. This section uses the term RMI to describe distributed object protocols in general. Java RMI is the Java language version of a distributed object protocol.

Get Enterprise JavaBeans, Third Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.