Diseñar un Interface Remoto

En el corazón del motor de cálculo hay un protocolo que permite que se le puedan enviar trabajos, el motor de cálculo ejecuta esos trabajos, y los resultados son devueltos al cliente. Este protocolo está expresado en interfaces soportados por el motor de cálculo y por los objetos que le son enviados.

El protocolo del motor de cálculo en acción.

Cada uno de los interfaces contiene un sólo método. El interface del motor de cálculo Compute, permite que los trabajos sean enviados al motor, mientras que el interface Task define cómo el motor de cálculo ejecuta una tarea enviada.

El interface compute.Compute define la parte accesible remotamente - el propio motor de cálculo. Aquí está el interface remoto con su único método:

package compute;

import java.rmi.Remote; 
import java.rmi.RemoteException;

public interface Compute extends Remote {
    Object executeTask(Task t) throws RemoteException; 
}
Al extender el interface java.rmi.Remote, este interface se marca a sí mismo como uno de aquellos métodos que pueden ser llamados desde cualquier máquina virtual. Cualquier objeto que implemente este interface se convierte en un objeto remoto.

Como miembro de un interface remoto, el método executeTask es un método remoto. Por lo tanto, el método debe ser definido como capaz de lanzar una java.rmi.RemoteException. Esta excepción es lanzada por el sistema RMI durante una llamada a un método remoto para indicar que ha fallado la comunicación o que ha ocurrido un error de protocolo. Una RemoteException es una excepción chequeada, por eso cualquier método Java que haga una llamada a un método remotor necesita manejar esta excepción, capturándola o declarándola en sus clausula throws.

El segundo interface necesitado por el motor de cálculo define el tipo Task. Este tipo es utilizado como un argumento del método executeTask del interface Compute. El interface compute.Task define el interface entre el motor de cálculo y el trabajo que necesita hacer, proporcionando la forma de iniciar el trabajo:

package compute;

import java.io.Serializable;

public interface Task extends Serializable { 	
    Object execute(); 
}
El interface Task define un sólo método, execute. Este método devuelve un Object, y no tiene parámetros ni lanza excepciones. Como este interface no extiende Remote, el método no necesita listar java.rmi.RemoteException en su clausula throws.

El valor de retorno de los métodos executeTask de Compute y execute de Task es declarado como del tipo Object. Esto significa que cualquiera tarea que quiera devolver un valor de uno de los tipos primitivos de Java (como un int o un float) necesita crear un ejemplar de la clase envolvente equivalente para ese tipo (como un Integer o un Float) y devolver ese objeto en su lugar.

Observamos que el interface Task extiende el interface java.io.Serializable. El RMI utiliza el mecanismo de serialización de objetos para transportar objetos entre máquinas virtuales. Implementar Serializable hace que la clase sea capaz de convertirse en un stream de bytes auto-descriptor que puede ser utilizado para reconstruir una copia exacta del objeto serializado cuando el objeto es leído desde el stream.

Se pueden ejecutar diferentes tipos de tareas en un objeto Compute siempre que sean implementaciones del tipo Task. Las clases que implementen este interface pueden contener cualquier dato necesario para el cálculo de la tarea, y cualquier otro método necesario para ese cálculo.

Así es cómo RMI hace posible este sencillo motor de cálculo. Como RMI puede asumir que los objetos Task están escritos en Java, las implementaciones de los objetos Task que anteriormente eran desconocidas para el motor de cálculo son descargadas por el RMI dentro de la máquina virtual del motor de cálculo cuando sea necesario. Esto permite a los clientes del motor de cálculo definir nuevos tipos de tareas para ser ejecutadas en el servidor sin necesitar que el código sea instalado explícitamente en dicha máquina. Además, como el método executeTask devuelve un java.lang.Object, cualquier tipo de objeto Java puede ser pasado como valor de retorno en una llamada remota.

El motor de cálculo, implementado por la clase ComputeEngine, implementa el interface Compute, permitiendo que diferentes tareas le sean enviadas mediante llamadas a su método executeTask. Estas tareas se ejecutan utilizando la implementación de task del método execute. El motor de cálculo devuelve los resultados a su llamador a través de su valor de retorno: un Object.


Ozito