martes, junio 29, 2004

Broker

La necesidad tiene cara de hereje, y para reiventar la rueda siempre hay suficientes tentaciones, por lo que hacer un Broker de mensajes desde 0 no merece el infierno ¿verdad? ¿O si? De todos modos lo tuve que hacer en un cliente para poder integrar en una aplicación diferentes sistemas de mensajería.

Los sistemas a interactuar eran diversos: MQ, JMS, WebMethods, XML-RPC y ficheros. La idea era que se pueda recibir mensajes de forma transparente desde la aplicación, es decir, que el destinatario no se entere ni del método de transporte ni del origen del mensaje. También era necesario poder enviar los mensajes por mas de un sistema a la vez.
Estos diferentes métodos deberían ser plugeables mediante la configuración de un fichero (mas adelante sería una consola).

Una ventaja adicional al producto resultante, sería que los developers podrían simular el envío de mensajes durante el desarrollo y las pruebas del software mediante ficheros, uno de los métodos de mensajería propuestos.

Sistema propuesto:


La clase principal es Broker, a la cual se la puede configurar con diferentes implementaciones de BrokerConnctorIF.

En el diagrama se pueden ver tres de estos conectores: FilesConnector, WebMethodsConnector y XmlRpcConnector. Cada una de estas implementaciones sabe como conectarse con su fuente de mensajes, un host en el caso de WebMethods por ejemplo, o una estructura de archivos en el caso del FilesConnector. Cada vez que reciben un mensaje, lo encapsulan en un BrokerMessage y lo entregan al Broker.

El Broker utiliza los BrokerMessageConverters (implementadores de BrokerMessageConvertersIF) que tenga configurados para convertir mensajes al "formato" necesario para determinada cola. Esto último ha servido para hacer unmarshalls de XMLs recibidos de distintas fuentes y tener asi objetos ya hidratados y listos para que el subscriptor del mensaje lo entienda.

Por último, los clientes del Broker, implementan la interfaz BrokerSubscriptorIF, la que les permite obtener los mensajes en forma asíncrona.

El siguiente paso es configurar las clases utilizando Spring y su IoC.

Primero los BrokerMessageConverters:

<bean id="postAddOrder" class="application.converters.PostAddOrderConverter" init-method="init"/>
<bean id="postConsolidateConverter" class="application.converters.PostConsolidateConverter" init-method="init">
  
<property name="xmlUtils"><ref bean="xmlUtils"/></property>
</bean>
<bean id="xmlUtils" class="adapter.utils.xml.CastorImpl" init-method="init">
  
<property name="mappingFileNames">
    
<list>
      
<value>castorMappings/Order.xml</value>
      
<value>castorMappings/LoadTendered.xml</value>
    
</list>
  
</property>
</bean>

Como ven uno de los conversores utiliza Castor. La idea es que el conversor sea capaz de convertir un InputStream en una clase determinada.

Los conectores:
<bean id="webMethodsBrokerConnector" class="utils.broker.connectors.WebMethodsConnector" init-method="init">
  
<property name="host"><value>${host}</value></property>
  
<property name="brokerName"><value>Name</value></property>
  
<property name="clientGroup"><value>Group</value></property>
  
<property name="clientName"><value>Client</value></property>
  
<property name="persistent"><value>true</value></property>
  
<property name="autoReconnect"><value>true</value></property>
  
<property name="sharedQueue"><value>true</value></property>
  
<property name="sharedOrderNone"><value>true</value></property>
</bean>

<bean id="filesBrokerConnector" class="utils.broker.connectors.FilesConnector" init-method="init">
  
<property name="home"><value>/data/brokerFiles</value></property>
  
<property name="xmlUtils"><ref bean="xmlUtils"/></property>
</bean>

<bean id="xmlRpcBrokerConnector" class="utils.broker.connectors.XmlRpcConnector" init-method="init">
  
<property name="port"><value>81</value></property>
</bean>

Cada conector tiene la configuración necesaria para conectarse con su fuente de mensajes e implementan la interface BrokerConnectorIF.

Solo nos queda ahora configurar "el" Broker, al cual se le especifica que conversores y conectores utilizará:

<bean id="messageBroker" class="utils.broker.Broker" init-method="init" destroy-method="close" lazy-init="false">
  
<property name="home"><value>/data/brokerFiles</value></property>
  
<property name="messageTTL"><value>1800000</value></property>
  
<property name="backupMessages"><value>true</value></property>
  
<property name="xmlUtils"><ref bean="xmlUtils"/></property>
  
<property name="converters">
    
<map>
      
<entry key="xx::Q1::postAddOrder">
        
<ref bean="postAddOrderConverter"/>
      
</entry>
      
<entry key="xx::Q2::postConsolidate">
        
<ref bean="postConsolidateConverter"/>
      
</entry>
    
</map>
  
</property>
  
<property name="connectors">
    
<list>
      
<ref bean="filesBrokerConnector"/>
      
<ref bean="xmlRpcBrokerConnector"/>
    
</list>
  
</property>
  
<property name="ignoreList">
    
<list>
      
<value>Event::error</value>
    
</list>
  
</property>
</bean>

Como se ve, una arquitectura de "plug-ins" como esta, junto con la invaluable ayuda de Spring, hace que implementar una clase a la cual se le agrega o quita funcionalidad dependiendo de las necesidades es muy simple.

viernes, junio 25, 2004

Incluir ficheros con Ant 1.6

Finalmente podemos incluir ficheros con Ant. Antes (Ant<1.6) solo era posible utilizar las facilidades de inclusión de entities de XML para hacerlo:

<!DOCTYPE project [
    <!ENTITY common SYSTEM "file:./common.xml">
  ]>

<project ...>
  &common;
  ...
</project>

Esto suponía no poder utilizar propiedades para especificar el nombre del fichero a incluir, y que el XML a incluir sea solo un fragmento (sin elemento root), por lo que se hacía difícil de manterer con herramientas de edición de XML.

Ahora, con Ant 1.6, tenemos acceso a una nueva tarea: import.

<project ...>
  <import file="common.xml"/>
  ...
</project>

No olvidar que ahora el fichero importado debe contener el tag <project>.

Mas info sobre las nuevas funcionalidades de Ant 1.6 en el artículo de Stefan Bodewig New Ant 1.6 Features for Big Projects.

miércoles, junio 23, 2004

Refactoring

Recientemente me vi obligado a recodificar una aplicación en un cliente. Y digo "me vi obligado" porque mi conciencia no me permitía dejarla así.

Si bien en su momento el desarrollo cumplió su función perfectamente, y quienes la hicieron estaban "empujados" por la necesidad de tener algo rápido (les resulta familiar?), ya era tiempo de darle un nuevo "toque" de modernidad, que a la vez le permitiera seguir creciendo.

Esta pequeña aplicación tiene como meta transferir información entre los sistemas propietarios del cliente y el paquete Transportation Planning & Management de i2, a la vez que controla y modifica algunos aspectos de esa información.

Pues bien, luego del refactoring aplicado, una de las medidas que utilicé para probar y demostrar que existía la necesidad de hacerlo, fue la cantidad de clases:



AplicaciónClases de dominioClases utilitariasTotal
Original9330123
Nueva354479


¿Nada mal un 35% menos, verdad? También podemos considerar que antes había 0.3 clases de servicios por cada clase de dominio, y ahora 1.25.

Pero vayamos a lo importante, el reemplazo de clases propietarias por open source y patterns.
Todo lo que había en la aplicación era hecho a medida. Todo. ¡Habían unas 20 ruedas 20 veces reinventadas!

Las clases encargadas del scheduling fueron remplazadas por Quartz.

La persistencia, implementada en JDBC directamente, se rehizo con Hibernate.
Y para la configuración de la aplicación y la dependencia entre objetos por supuesto se utilizó Spring, que por otra parte ofrece un soporte inmejorable para Quartz e Hibernate.

En algunos casos se necesitaba también un número limitado de threads ejecutándose al mismo tiempo, por ejemplo no mas de 4 ftps simultáneos en determinado servidor. Para ello, nada mejor que commons Pool.

Para el pool de conexiones a base de datos, utilizado por hibernate, se utilizó commons DBCP. Es muy interesante destacar que en utilizando ls facilidades de IoC de Spring, cambiar este pool por otro, por ejemplo el del servidor de aplicaciones, es muy sencillo.

Para cache de la base de datos utilicé EHCache, que viene prácticamente integrado con Hibernate.

Y como quiero tener siempre una base de datos ejecutándose localmente durante el desarrollo, utilicé MySql, para luego cambiar a Oracle(la base del cliente) durante las pruebas de integración en adelante.

Dado que Spring basa su framework en la utilización de POJOs, el paquete commons beanutils resultó de gran utilidad.

La aplicación utiliza varias formas de conectarse con el resto de mundo: WebSphere MQ, webMethods, ftp(commons net) y webServices (Apache XML-RCP).

Exolab Castor es un data binding framework, y fue de gran utilidad la funcionalidad Java-to-XML binding es decir, la capacidad de hidratar/deshidratar un objeto a y desde XML.

Y por supuesto el infaltable log4j, mediante commons logging.

También he aprovechado para crear los test cases y pruebas unitarias, por lo que necesité utilizar lo stándard en estos momentos: JUnit, junto con DBUnit para la configuración de la base de datos durante las pruebas y JUnitDoclet para la generación automática de la base de los TestCases.

Como Hibernate tiene ficheros de configuración donde se mapea cada propiedad de cada clase que necesitas persistir a su respectiva tabla/columna, la creación/mantenimiento de estos .hbm puede ser muy tediosa. Pero para eso existe XDoclet, que se dedica a la creación de código en base a @tags en el codigo fuente, lo que se llama Attribute-Oriented Programming.

Claro que todo esto no sería nada sin la documentación adecuada de todos (si, todos) los javadocs. En este caso utilicé también java2html para convertir los .java a html, con todo el formato necesario, para su inclusión, mediante un link, en los javadocs.

Y finalmente Ant para ejecutar diversos pasos del proceso de desarrollo, sobre todo en la generación de código (XDoclet, JUnitDoclet), en la documentación (javadocs, java2html), en el deploy (Jar, War, Ear), en la ejecución de los TestCases (JUnit, DBUnit) e incluso en la "verificación de estilo" que proveen Hammurapi o CheckStyle.

En cuanto a la utilización de patterns, voy a extrañar el Singleton gracias a Spring :), pero el resto es de increíble utilidad a la hora de tener soluciones previamente "pensadas". Todo el mundo debería empezar por patterns primero y aprender Java mas adelante.

Como ven, no hay nada nuevo bajo el sol, pero siempre es bueno comprobar que se puede utilizar efectivamente opensource y buenas prácticas (o por lo menos prácticas estándard) en beneficio de un cliente corporativo.

martes, junio 22, 2004

Variables lifecycle

Ante una pregunta inocente sobre la "elegancia" de utilizar

  String s = null;
  while (true) {
    s = "...";
  }

o

  while (true) {
    String  s = "...";
  }

a un experto en la materia (EV), la respuesta fué la siguiente:

En realidad sería asi: los corchetes {} marcan un scope. Adentro de ese scope la variable existe, afuera no. Fijate que es lo mismo que al entrar a un método, solo que no llama la atención porque uno se acostumbra a verlo de otro modo:

  void m() {
    int a = 0
    String s = ""
  }


a y s valen dentro del {} que delimita el cuerpo del método. Pero los {} se pueden usar "sueltos" para agrupar scopes. Esto es válido (e inútil):

  void m() {
    {
      int a = 0;
      String p = "";
    }
  }


También se puede hacer esto:

  void m() {
    String s;
    {
      int a = 0;
      // aca existen "a" y "s"
    }
    {
      String p = "";
      // aca adentro "a" no existe; pero "s" sí.
    }
  }


Las operaciones de la VM son las mismas cuando abre cualquier corchete, ya sea el del cuerpo de la función, el cuerpo de un while o corchetes sueltitos: alocar espacio para las variables locales a ese {} en el stack al abrir, y liberarlo al cerrar.

Ya que estamos, una disgresión. Ya que entre {} hay un bloque de código autónomo, por que corno no se puede hacer esto?:

  Code c = {int a=0; System.out.print.... }


y despues

  c.evaluate();


Sería muy lindo que los {} sean objetos también. Smalltalk tiene eso y es absolutamente la única razón (a mi juicio) por la cual el Smalltalk es tan cómodo.
Esas cosas se llaman "clausuras" de código, y elegantizan mucho los programas. En programación funcional también existen y son muuy útiles. Son como tener funciones en stand-by. El tema es que la implementación óptima es muy compleja, por eso los lenguajes "industriales" no los implementan.