experiencia
Servicios - Sepa lo que Ricardo Devis & Asociados pueden hacer por ustedPublicaciones - Consulte los documentos que ponemos a su disposiciónContacto - Conozca como ponerse en contacto con Ricardo Devis & Asociados
 


RPP abril 1997

Todo Java (VI): Hacia el software distribuido

Ricardo Devis Botella

La clara y masiva influencia de Internet ha conseguido que la noción de servidores/entes/objetos remotos resulte familiar a una vasta mayoría de aficionados. Claro que también ha conseguido que las imbecilidades, antes remotas, invadan sin pudor los paginadores caseros. Las distancias parecen acortarse, y surge, natural, el uso de porciones software de localización indeterminada en la red. Y, claro, ahí está Java para darle forma técnico-comercial actual a estos viejos esquemas.

 

En este deformado campo pseudo-científico que es la informática los postulantes suelen a menudo preguntar "¿cómo?" y muy pocas veces "¿por qué?". Denle, si no, a un programador una -para él- nueva tecnología, y obtendrán dos respuestas posibles: bien el desprecio total (acompañado por la expresión universal de la estupidez), bien la petición de un manual para aprender cómo usarla [1]. Siempre el "¿cómo?" Y casi nunca los "¿dónde?", "¿cuándo?" o "mañana, sin falta, empezaré mis clases de inglés". Lo malo es que los autores de manuales informáticos (ya saben, esa infausta horda de posibilistas) han adoptado tales quereres como único estilo y, lo que es peor, han contagiado al resto de los autores. Esto viene a cuento porque recientemente aconsejé a un colega que, si quería estar al tanto de lo que se cocía en informática, empezara a sumergirse en CORBA y en esquemas de software distribuido. Tras dos meses sus conclusiones fueron: "Compré varios libros, leí distintos artículos y examiné el web. Todos ellos detallan, con mayor o menor gracia, las mismas características: arquitectura, servicios, ORBs, interoperabilidad, etc. Pero es como si al asistir a un pase de " Les vacances de Monsieur Hulot ", fuera sustituido por su correspondiente "Cómo se hizo...": o sea, no disfruto, no lo entiendo del todo y me siento estafado ". Lo cierto es que a los informáticos-de-a-pie les suele gustar la vertiente práctica, y los textos de informática distribuida parecen detallarnos la quinta dimensión con el aplomo con que mi abuelo dictaba la receta de la gachamiga. Nos haría falta un ejemplo del mundo real que nos permitiera asimilar adecuadamente, en trazo grueso, el funcionamiento de las llamadas a objetos remotos y, cómo no, la esencia y calidad de los intervinientes en la resolución de tales llamadas. Lo malo es que, tras pensar un rato, no encontré un buen ejemplo "real" alejado de la informática, así que... ¿por qué no un ejemplo "supra-real"? Y, sobre todo, ¿por qué no un ejemplo comprensible, antes de entrar en materia? Verán, con todo, que el ejemplo resulta más ajustado a la realidad de lo esperado. El lector enterado constatará también que el símil es más javiano que córbico, pero es que la pedagogía impone aquí su ley. A ello.

EL MODELO DISTRIBUIDO ESPIRITISTA

¿Cómo podría comunicarme con mi perro, muerto un año atrás? ¿Cómo pedirle consejo al Marqués de Sade? ¿Cómo recibir clases anatómicas de Jack the Ripper? ¡Mediante la comunicación remota! O sea, a través de un médium. Claro que uno podría preguntarse: ¿Existen realmente los espíritus (objetos remotos)? Y, en caso afirmativo, ¿para qué demonios los necesitamos? Pospondré, no obstante, esta inquisición metafísica para que podamos atender a la secuencia de pasos necesaria para establecer la comunicación con un objeto del más allá, en adelante AEP (Alma en Pena):

  • Suponemos que una determinada AEP existe, o deberemos proceder a crear una nueva (usualmente mediante el asesinato por motivos científicos). Asumimos que la AEP implementa un interfaz remoto específico (ya saben, el asunto ectoplásmico, la comunicación mediante la tabla Ouija, etc.) y que, además, posee una implementación interna (o sea, que posee comportamiento propio).
  • Mediante una herramienta adecuada (un compilador de AEP remotas, una bola de cristal, alcohol, drogas, algo de dinero y buenas dosis de credulidad), debemos habilitar la comunicación entre mundos tan distantes. La solución es crear una suerte de telefonillo rústico, con latas a cada lado (una aquí y otra en el más allá) y un cable que las una, mezcla de intercomunicador y traductor (desafortunadamente las mismas latas no nos servirán para distintas AEPs). Así, si aplicamos la herramienta a la AEP obtendremos un intercomunicador (skeleton) en el más allá y una porción (stub) acá que se colará en el médium.
  • Pero la cosa no es tan simple, porque no hay líneas directas con las AEP: hay que pasar por una centralita, que habremos de suponer en funcionamiento para que pueda atender nuestras llamadas. Y si no fuera así habría que "inicializarla".
  • ¿Qué nos falta aquí? Pues la policía, claro está. El más allá tiene que habilitar un gestor de seguridad que impida transacciones de información no autorizadas o transmutaciones no esperadas.
  • El telefonillo está bien, pero necesitamos un número al que llamar. O sea, si el médium invoca a "Antonio, Antonio", ¿a quién demonios está llamando? Esto es, necesitamos en el más allá de un cierto registro (algo así como el Libro de San Pedro) que asocie o ligue (bind) un nombre a cada AEP (aunque no necesariamente a todas: ¿quién querría comunicarse con mi tía Enriqueta?). Y además necesitamos que nuestra AEP se registre en tal lista, a modo de cola de espera en una cabina telefónica.
  • Claro que si el más allá tiene sus cuerpos de seguridad, ¿vamos a ser menos acá? El médium establecerá un gestor de seguridad para evitar que espíritus inoportunos puedan dañar su sistema local o al cliente. Adicionalmente ciertos médiums procuran otros elementos de seguridad para impedir que el cliente se marche sin pagar.
  • Empieza la búsqueda (lookup) de la AEP remota: "¡James, James!", resoplará el médium. Como no contestará, el médium seguirá: "Oh, Espíritu del Averno, muéstrame a James, encerrado en las Cuevas de la Desesperación!". Y es que hay que especificar, pues con una URL concreta (//EspírituDelAverno:1099/CuevasDeLaDesesperación/James) es posible encontrar el objeto buscado.
  • Finalmente se establece la comunicación, pero la proliferación de mediums y el uso del espiritismo en paralelo (una misma AEP sirviendo a distintos clientes simultáneos, como en el caso de las AEP de Elvis o Hitler) han conseguido que las AEP sean muy celosas de su tiempo, así que anotan a quienes se ponen en contacto con ellas (reference counting), y eliminan la entrada al cerrar la comunicación. De esta forma pueden desaparecer (garbage collecting) cuando no estén siendo referenciadas por ningún cliente.
  • Todo está listo, pero ¿qué queremos hacer con la AEP tras tanto preparativo? Lo suyo hubiera sido que lleváramos una lista de invocaciones y peticiones, que dirigiremos a la AEP remota por medio del médium, que actúa como proxy de la AEP. Naturalmente estamos hablando del uso por parte del cliente de la funcionalidad de una AEP remota: o sea, de las llamadas del cliente.
  • Hasta aquí el contubernio preparado (estático) de la comunicación remota. Claro que sería posible que un cliente no dispusiera de médium, y al invocar con ganas a la AEP ésta le mandará el telecomunicador (stub) correspondiente: nos encontraríamos aquí con una conexión dinámica. Claro que si estamos prestos a recibir cualquier cosa (cualquier stub sin referencias previas), nuestro cerebro (sistema local) puede acabar dañado, o lo que sería peor: podríamos convertirnos en pacientes psiquiátricos, junto con los abducidos y los seguidores de Ted Anger.

La comunicación remota posee, como resultará evidente a cualquiera, más riesgos y problemas que la local, y las dificultades aparecerán más sutiles. Imaginen que el médium pierde momentáneamente la conexión con el más allá: cuando recupere su estado de trance pensará que el AEP anterior sigue al otro lado, cuando quizás haya desaparecido. Las excepciones e irregularidades que se pueden presentar en una conexión deben, pues, ser previstas en el entorno cliente (esto, en el mundo real, se traduce por lo que los estafadores llaman "escenarios de simulación interactiva", se adereza con "representaciones de ventrílocuos" y se sazona con mensajes del tipo "Se ha producido un error inesperado en el trance místico: módulo VXD666.SYS"). Pero, en fin, volvamos al mundo real. Perdón, quise decir al mundo Java.

UNA PREMISA FORZADA

Oh, hay muchas razones para aconsejar el acceso remoto a cierto tipo de objetos, pues estos pueden, por ejemplo, contener cierta lógica que no se desea hacer accesible a los clientes (sólo sus resultados), o bien no se conoce, dentro de una familia de objetos -jerárquica o no- cuál de ellos encaja en el perfil del cliente, no siendo posible, por razones de seguridad, disponer de todos ellos en la parte local; es posible, por otro lado, que no exista capacidad de almacenamiento en el hardware cliente (como en los NCs), como también es probable que los esquemas de almacenamiento de una empresa prevean cambios en los servidores que no hayan de afectar a los clientes; pueden darse, también, una centralización de datos, software cliente monolítico e interfaces de aplicación distribuidos por una (quizás "la") red. Claro que todos estos borrosos ejemplos suenan un tanto asépticos, demasiado irreales tal vez. Pero tal no ha de preocupar al lector, que quizás también confíe en el futuro predominio de Java y que posiblemente piense que el software mejora día tras día. Y es que, entre tanta informática, la realidad parece escapársenos, liviana. En fin: créanme si les digo que el software distribuido es un futuro que se acerca al presente con gran celeridad. Y aprobado el axioma, veamos qué tiene que decir JavaSoft al respecto.

RMI Y CORBA

RMI ( Remote Method Invocation ) es un API que permite invocar métodos de objetos Java pertenecientes a distintas máquinas virtuales, mayormente constituido por un conjunto de interfaces Java y perfectamente integrado en la plataforma Java JDK 1.1.X (¿Y JDK 1.0.X? Humm, han corrido rumores que JavaSoft piensa castigar con cursos de CORBA a los que sigan haciendo este tipo de desfasadas preguntas. Ya sabe, lector: el mundo empieza de nuevo, y comienza en JDK 1.1.X). El API de serialización (Serialization), también incluido en JDK 1.1.X, es de vital importancia para RMI, pues permite convertir un objeto en un flujo (stream) de bits para su transporte por la red, y posteriormente recuperarlo y convertirlo de nuevo en un objeto Java operativo. "¡Alto!" -rugirá aquí el lector entrenado-. "¿CORBA no ha sido creado precisamente para esto: para la interoperación entre objetos de desconocida implementación y ubicaciones remotas (por desconocidas)?". Vaya, el exacto conocimiento de este lector me ha turbado. En efecto: CORBA (Common Object Request Broker Architecture) es el mecanismo de comunicación entre objetos de la OMA (Object Management Architecture), normalizaciones ambas creadas por el/ la OMG (Object Management Group), el único organismo actual de estandarización cuyos dictámenes son seguidos muy de cerca por la industria. Pero CORBA se diseñó pensando en la interacción entre objetos implementados en distintos lenguajes, previendo la evidente necesidad de interoperar con "legacy objects: objetos heredados", posiblemente codificados en COBOL, C, etc. A tal fin CORBA provee interfaces codificados en IDL (Interface Definition Language), que permiten el intercambio de mensajes entre objetos de desconocida implementación. A la vez, CORBA incluye poderosas especificaciones de interoperabilidad entre ORBs (en CORBA 2.0), un potente servicio de nombres (incluido con otros como notificación de eventos y gestión transaccional en los denominados "Servicios de Objetos"), Facilidades Comunes (Common Facilities) e Interfaces de Dominio (Domain Interfaces), o sea, especificaciones dirigidas a segmentos verticales de negocios. Aunque entrevisto con rapidez, OMA incluye mucha enjundia, pues tiene que lidiar con el general caso de interoperabilidad entre elementos desconocidos. RMI, por el contrario, parte de un presuntuoso supuesto: todo es Java. O sea, no hay necesidad de un lenguaje especial de interfaces, pues no hay objetos implementados en un lenguaje distinto de Java. No se dan, tampoco, los costosos supuestos de la gestión de objetos remotos, pues la correspondiente máquina virtual Java se encargará de ellos. Pero, esperen, tracemos un pequeño mapa de interoperabilidad:

  • Los componentes-objetos Java pueden llamarse unos a otros mediante la invocación directa de métodos y también utilizando el nuevo esquema de notificaciones de eventos del JDK 1.1.X.
  • Los JavaBeans pertenecientes a una misma máquina virtual Java pueden compartir datos e interfaces (publicarlos, producirlos, anunciarlos, consumirlos o controlarlos) en el mismo "InfoBus", que es una tecnología IBM-Lotus-Kona adoptada recientemente por JavaSoft y próxima a ser incluida en próximas versiones del JDK.
  • Los JavaBeans y/o objetos Java pueden invocar métodos en objetos/JavaBeans pertenecientes a distintas JVMs (Java Virtual Machines) por medio de RMI y serialización.
  • Cualquier objeto Java podrá relacionarse con objetos codificados en lenguajes no necesariamente Java mediante CORBA.

Lo bueno, para tranquilidad del lector, es que JavaSoft prometió en su día, y cumple ahora, el soporte total de CORBA e IIOP (Internet Inter-ORB Protocol), de manera que podrá usarse bien CORBA bien RMI sobre IIOP para entornos puros Java. Pero sigamos.

RMI: JAVA A-LA-REMOTE

Lo que RMI consigue es extender las conocidas llamadas locales Java hasta los intefaces Java que publican servidores remotos. Las diferencias básicas respecto del modus operandi local residen en:

  • Se incorporan, como consecuencia de las nuevas posibilidades de error (como, por ejemplo, que el tipo de retorno de un objeto remoto corresponda a una clase no serializable), nuevas clases para el manejo de excepciones (java.rmi.RemoteException)
  • Las llamadas a métodos remotos se realizan siempre a través de interfaces.
  • En las invocaciones a métodos remotos, los parámetros no-remotos se pasan por copia (serializados en flujos de bits [2]), mientras que los argumentos remotos se pasan por referencia. Pero no piense el lector que se trata aquí del paso por referencia de Pascal o de C++. En realidad tal paso por referencia supone, en un método local, la copia de la dirección de memoria que apunta al espacio de memoria que contiene el objeto, mientras que en un método remoto se copiaría (mediante serialización, claro está) el stub de la parte cliente correspondiente al objeto remoto.
  • Se da, además, una implementación remota de la clase Object, denominada RemoteObject.
  • La recolección de basura por parte del servidor utiliza un mecanismo de "reference counting" que incrementa o decrementa un contador según se producen enlaces provenientes de invocaciones remotas de clientes.

Naturalmente el proceso de creación de las clases envueltas en un esquema de invocaciones remotas cambia sobremanera. Claro que la mente retorcida de los programadores encontrará en esta nueva y más extensa secuencia una fuente de errores mejor que la anterior en entornos locales.

PROGRAMACIÓN RMI

  • En primer lugar necesitamos crear un interfaz remoto para la parte servidora, de forma que los clientes puedan acceder, por medio de tal interfaz, a la funcionalidad remota necesaria. Tal interfaz deberá extender el interfaz "java.rmi.Remote", y cada uno de sus métodos deberá especificar el lanzamiento de al menos la excepción "java.rmi.RemoteException".

public interface InterfazPolítico extends java.rmi.Remote {

// String (el tipo de retorno) es "serializable", como lo

// son todos los tipos incluidos en la plataforma Java

String habla() throws java.rmi.RemoteException;

}

  • Tras esto, y siempre en el servidor, debemos crear una clase que implemente el anterior interfaz remoto:

import java.rmi.*

import java.rmi.server.UnicastRemoteObject

// La derivación de nuestra clase respecto de UnicastRemoteObject

// significa que, al invocarse el constructor de tal superclase

// en el constructor de la presente, originará que el servidor

// "exporte" el objeto remoto, atendiendo desde entonces las

// llamadas remotas que sobre él se generen. El tipo concreto de

// servidor UnicastRemoteObject se caracteriza por ser un objeto

// remoto de tipo Singleton, de forma que las referencias a él

// dirigidas son válidas únicamente durante la vida del proceso

// que lo creó; por necesitar transporte TCP y por usar un

// protocolo de flujo (stream) para el paso de parámetros,

// invocaciones y resultados.

public class Político extends UnicastRemoteObject

// y seguidamente implementa el interfaz remoto

implements InterfazPolítico {

// El constructor inicializará convenientemente los

// atributos internos del objeto.

public Politico() throws RemoteException {

// Aquí se llama al constructor sin argumentos de la clase

// base. O sea, se produce una llamada implícita a "super()"

// Y seguidamente vendría el cuerpo del constructor

// ...

}

// Sigue la implementación del método del interfaz remoto,

// conservando la signatura y la especificación de errores

// Como el lector sabe, la clase debe implementar todos los

// métodos del interfaz.

String habla() throws RemoteException {

return "Java va bien. RMI va bien. España va bien.";

}

// lo que hay que hacer ahora es dotar a nuestra clase de

// una función "main" que posibilite la creación de una

// o más instancias de la clase, sujetas al control de un

// gestor de seguridad apropiado.

public static void main( String args[] ) {

// RMI supone la transferencia entre distintas

// máquinas virtuales Java de stubs, objetos y, en

// general, código Java, por lo que es necesario

// habilitar un gestor de seguridad que controle

// tales aspectos. En caso contrario el sistema no

// permitirá ninguna carga de código Java no local.

// En el caso que el código cliente invocante fuera

// un applet, el gestor de seguridad sería, por

// defecto, el AppletSecurityManager.

System.setSecurityManager( new RMISecurityManager() );

// Nos preparamos para captar posibles errores

try {

// Ahora creamos una instancia del objeto remoto

// que se exportará debido al constructor de su

// clase base, UnicastRemoteObject

Político xema = new Político();

// A continuación hay que ligar (bind) el objeto

// remoto a un nombre. Podríamos usar el método

// bind(...), pero éste podría arrojar una

// excepción del tipo AlreadyBoundException si

// el nombre elegido ya estuviera asociado a otro

// objeto. Lo usual es utilizar el método rebind,

// que actualiza la asociación si ésta ya existe,

// y si no existe la crea. En realidad lo que

// el código genera es una entrada de Diccionario

// en el "Registry" (o su actualización).

// Naturalmente se supone que el servidor ha

// inicializado el registro con una orden del

// tipo: "rmi registry".

// Opcionalmente puede significarse un puerto

// en la URL, tras el nombre del servidor. En

// caso contrario el puerto por defecto es 1099.

Naming.rebind( "//Parlamento/poli", xema );

} catch (Exception error ) {

System.out.println( "Error político: " +

error.getMessage() );

error.printStackTrace();

}

}

}

  • A continuación debemos compilar nuestra clase "Político" utilizando "javac", el compilador Java (o cualquier herramienta de programación Java con compilador embebido, claro). Seguidamente debemos aplicar el compilador de stubs (rmic) a los ficheros de clase (.class) generados por javac, obteniendo dos nuevos ficheros de clase: un stub, que servirá de proxy en la parte cliente redireccionando las invocaciones hacia el servidor, y un skeleton (esqueleto) en la parte servidora que recibirá las llamadas remotas e invocará la correspondiente implementación.
  • Debemos ahora crear el código cliente que haya de realizar las invocaciones remotas:

import java.rmi.*

public class Votante {

// un atributo tipo String para tomar el pulso a la nación J

String situaciónPolítica;

// la función main: no estamos en un applet

public static void main( String args[] )

// Hay que crear e instalar un gestor de seguridad RMI

// que colaborará con el gestor equivalente en el servidor

System.setSecurityManager( new RMISecurityManager() );

try {

// El método lookup(...) buscará en el registro

// la asociación de la URL pasada como argumento con

// la referencia de un objeto remoto (en el servidor).

// En el caso que quisiéramos buscar un determinado

// registro, podríamos usar el método getRegistry

// de la clase LocateRegistry para determinar el

// servidor e incluso el puerto.

Político miDiputado = (Político)Naming.lookup(

"//Parlamento//poli" );

// Lo que sigue es, simplemente, la invocación del

// método habla() en el objeto remoto de tipo Político.

// ¡Oh, Sancta Simplicitas!

// Con todo, lo que aquí se obtiene es una anodina

// frase sobre la bondad de la situación general, frase

// que circulará como un String serializado desde el

// servidor al cliente.

situaciónPolítica = miDiputado.habla();

} catch( Exception error ) {

System.err.println( "Error de Sistema: " + error );

}

}

  • Resta sólo compilar nuestra clase cliente y ejecutarla.

UNA ADVERTENCIA REMOTA

El esquema anterior resulta bastante sencillo. Incluso más de un lector puede verse tentado a empezar a usar las facilidades de distribución de código que procura la RMI. ¡Alto ahí! Si la mayoría de programas adolecen de un adecuado análisis y diseño, imaginen el estado de programas en los que, además, hay que distribuir cierta parte de la funcionalidad, del comportamiento, de los atributos y de la inteligencia entre distintos servidores y clientes. A esto se une el hecho de que los actuales métodos de análisis y diseño orientados-a-objetos (obviaré el resto de métodos, claro) no están "preparados" para el software distribuido (con una notable excepción: Evo-Fusion, la versión con capacidades de distribución y trabajo en equipo de el clásico método Fusion).

DINAMISMO DISTRIBUIDO

Hemos visto que la aplicación del compilador rmi (rmic) genera stubs y skeletons para las clases afectadas, de forma que podremos preparar nuestras invocaciones con todo detalle. ¿Qué ocurre, sin embargo, si lo que deseamos es trabar comunicación con objetos remotos desconocidos? Se trata, como ya habrá adivinado el lector, de la promiscuidad distribuida, o de las conexiones intergalácticas, como gusta decir Orfali (que también habla del Object Web). Seguimos necesitando un stub en la parte cliente (suponemos el skeleton en el servidor, como suponemos la implementación del objeto remoto y el resto de requisitos cumplidos), pero podemos aprovechar las características de Java para descargar (download) del servidor el stub requerido, y así comenzar la comunicación. El lector ha de tener en cuenta que aunque yo no creo que la promiscuidad sea la madre de las enfermedades, tal vez sea la sobrina. O sea: exponerse a cargar cualquier código Java desconocido (porque, al fin, los stubs son eso) en nuestro sistema local es exponerse demasiado. Lo ideal sería restringir la descarga de stubs a servidores especificados en la configuración local. Pero claro, esto no es tan intergaláctico. El lector ha de tener en cuenta, en todo caso, que los bytecodes de implementación de los objetos remotos no viajan del servidor al cliente: sólo viajaría el stub.

PRÓXIMAMENTE

razón de un artículo por mes, cuando nos encontramos a mitad de la serie resulta que nos han cambiado el lenguaje, han modificado ciertas estrategias, se han añadido nuevas capas tecnológicas, se han producido ciertas sinergias. En fin: que el mundo Java está sobreexcitado. Quedan, así, todavía muchas cosas que contar: Java y CORBA (mi favorito), JSQL (el nuevo estándar SQL de Oracle para Java y favorito en la carrera hacia el SQL-3), InfoBus (el Bus para JavaBeans de Kona), la herramienta de migración de ActiveX a JavaBeans, el enlace (binding) con bases de objetos, los Entreprise JavaBeans (JavaBeans con capacidades transacciones y de escalabilidad), etc. etc. Y resta, sobre todo, que les aconseje una larga lista de libros sobre Java, con mis comentarios y mis despechos. Queda, en fin, mucho Java que contar.

[1] En realidad los manuales cumplen una función más sofisticada respecto de los programadores, pues éstos, tras leer el índice y señalar algunas hojas con papelitos, pueden situar los textos perennemente en su mesa de trabajo y reducir así el espacio útil destinado a los útiles de trabajo efectivo. Además la posesión del manual entraña un sutil aviso, similar al que despide la orina de los canes, respecto del área de dominio del programador.

[2] "Serializado", referido a un objeto, significa que ha sido convertido en un flujo de bits con suficiente información sobre sus atributos persistentes y clase Java, de la que es instancia, como para permitir una reconstrucción completa del mismo. Programáticamente supone la implementación por una clase del interfaz publico "Serializable". Sin más.

 
 
 
volver a la página de publicaciones
 
 
 Pº. Castellana 188, 14º e · 28046 - Madrid · info@a4devis.com