Diseñando una arquitectura (III/IV): Implantación

Para explicar esta etapa, la palabra es crisis. Pero no tanto la acepción extendida en la actualidad, sino el significado etimológico que recoge la Wikipedia

Coyuntura de cambios en cualquier aspecto de una realidad organizada pero inestable, sujeta a evolución; especialmente, la crisis de una estructura.
El éxito o fracaso de este cambio se producirá (no considero que ya se haya acabado este periodo de cambio) si hacemos una correcta gestión de la crisis. 

III. Implantación

En este apartado expondré cómo afrontamos este cambio, los problemas encontrados y las soluciones aportadas, y también los errores. Las oportunidades de mejora quedarán para el apartado IV, de conclusiones. Me intentaré ceñir a los hechos del cambio, dejando otras consideraciones para otro momento.

Entorno de desarrollo

El cambio del entorno de desarrollo consistía en dejar la combinación JDeveloper + OC4J como servidor local y SJSAS 8.1 en entorno de desarrollo compartido y sustituirlo por MyEclipse + SJSAS 8.2 en local.
Tras muchas pruebas vimos que la integración con el SJSAS tendría que ser mediante dos despliegues: uno packaged (internamente es generación del war + despliegue) inicialmente y para redespliegues, y uno exploded (internamente es copia de binarios al directorio de despliegue del servidor). Sin uno o sin otro el servidor no se da cuenta de los cambios.
Para hacer la migración preparé un escueto manual de instalación de ambas herramientas. Un mismo día hicimos que todos los desarrolladores (programadores y analistas) hiciesen el cambio. La verdad es que llevó bastante más de lo esperado. La instalación y configuración de ambos (adecuación al entorno interno y librerías), que por mi experiencia creo que se puede llevar a cabo en menos de media hora (yo lo he hecho, con un ordenador no especialmente rápido), en algunos casos rondó las dos horas, lo cual no me parece aceptable. Sin embargo, no recuerdo que ningún problema se debiese a otra cosa que no seguir las instrucciones. Redactarlas fue principalmente un ejercicio de repaso de lo hecho, que sirviese de guión para seguir todos los mismos pasos. Antes del cambio utilicé a un compañero de conejillo de indias, lo cual me sirvió para corregir los errores de la instalación, así que la versión definitiva usada estaba completa. IMHO ni siquiera debería haber escrito nunca el manual. La práctica totalidad de los desarrolladores somos ingenieros informáticos, un buen porcentaje ingenieros superiores (o en proceso de serlo), e instalar un IDE y un servidor no debería ser algo traumático. Sin embargo, quedó de manifiesto deficiencias en la formación (sin ánimo de ofender a nadie). Se puede salir de la carrera prácticamente sin programar, sin cacharrear, lo cual hace que nuestras aptitudes prácticas estén sin entrenar. A menudo incluso cuesta discernir entre lo que es un problema en el entorno de desarrollo y lo que es un problema en el servidor. Recomendaría exigir un trabajo más conciencudo en el proyecto de fin de carrera, de forma que este sirviese al menos para conocer las herramientas que se va a emplear al salir al mercado laboral, pero como, al menos en la Universidad de Valladolid, a "la competencia" se le regala la matrícula de honor en el proyecto, pues me parecería injusto. Antes de que surjan los trolls en los comentarios, por supuesto que hay ingenieros en telecomunicaciones realmente buenos, inteligentes, válidos y trabajadores, mucho mejores que muchos informáticos, pero el hecho es que a menos que insultes al tribunal durante la presentación, en esa carrera la nota por defecto del proyecto es matrícula de honor.

Una vez superado estos ligeros problemas iniciales, el día a día con estas herramientas ha tenido un problema: los redespliegues. No es algo exclusivo de estas herramientas, pero tener que reiniciar (o incluso redesplegar) un servidor con gran parte de los cambios es una pérdida de tiempo grandísima. Pronto detectamos que la gente hacía demasiados redespliegues. A menudo cada vez que algo no funcionaba se hacía un redespliegue o un reinicio, "por si acaso". Cada uno puede llevar uno o dos minutos como mínimo, y si vas sumando al final es mucho tiempo perdido, especialmente porque el "por si acaso" nunca sirve para nada. Para evitarlo, distribuimos los casos en los que, al menos con esta combinación de MyEclipse + SJSAS 8.2, es necesario hacer un reinicio o un redespliegue:
  • Si se añade, quita o modifica un método o atributo, o la declaración de una clase, el propio MyEclipse avisa de que es necesario reiniciar.
  • Si se modifican ficheros de JSF, Spring, Hibernate, Log4J, etc, es necesario reiniciar.
  • Si se modifica el web.xml, es necesario redesplegar.
  • En el resto de casos hacer otra cosa no servirá de nada. Si hay un problema, reiniciar o redesplegar no servirá de nada, así que ¡no lo hagas!
Para mitigar este problema se optó por orientar el desarrollo, hasta la capa de servicios, hacia las pruebas. Desarrollar orientado a pruebas no exige lentos reinicios o redespliegues de servidor, tan sólo relanzarla, que es mucho más rápido (aparte de las otras obvias ventajas).

El único problema grave que tuvo este cambio en sí (los problemas en la instalación fueron más sintomáticos que realmente costosos) fue el propio servidor. Casi nada (ni libreriás open source ni productos comerciales) tiene soporte para SJSAS. Todo se orienta a Tomcat y, si el producto es de JBoss, a su servidor. Comprendo que no es lo mismo para un administrador de un cluster de servidores Sun en producción tener SJSAS que tener Tomcat, pero para el desarrollador es un opción. Y desarrollar en Tomcat y después desplegar en SJSAS no es opción. Hay incompatibilidades, así, como suena. Basta con echar un vistazo a los foros para darse cuenta de ello.

Librerías del framework

Antes de desgranar los hechos por capas, debemos hablar en general del "lote" (JSF+Spring+Hibernate). Yo no soy ni mucho menos un experto en estas tecnologías, ni disponíamos de nadie con una gran experiencia en ellas, así que mi papel era conseguir dominarlas al máximo para poder solucionar los problemas que apareciesen. Ninguno de los programadores ni analistas tenía experiencia con ninguna de ellas. Por ello en estos dos años de investigación (creo que es como se debe denominar a esta tarea) he metido las manos en harina al máximo.
Para facilitar el cambio tomamos las siguientes medidas:
  • Inicialmente hicimos unas sesiones relámpago de introducción a estas tecnologías. Al menos deberían servir para responder a las preguntas "¿qué?", "¿para qué?" y "¿dónde me documento?" La pena es que sólo uno de los programadores que las siguió desarrolló con ellas en los siguientes meses (y además nos dejó en poco tiempo), por lo que cayeron en saco roto.
  • La contratación de nuevo personal se orientó a gente con experiencia en ellas o, al menos, con experiencia de años en diferentes tecnologías. Eso provocó alguna rotación indeseada, pero puso de manifiesto que es muy difícil conseguir personal realmente formado en tecnologías "avanzadas" de J2EE. Es difícil que a la llamada de "gente que sepa de Hibernate" aparezca gente, y aún en ese caso durante las entrevistas nos encontramos con muchos casos en los que en el CV aparecía dicha experiencia pero después no se sabía ni explicar qué era una LazyInicializationException.
  • Creación de wiki interno con documentación, FAQ, y guía de errores.
  • Todas las dudas se resolverían a través de un foro en vez de mediante teléfono o correos. De esta forma todo quedaría documentado.
Sobre la formación sólo puedo decir que no lo hicimos (o no lo pudimos hacer) como se debería. No dedicamos tiempo específico a formarnos, por lo que los problemas los ibamos resolviendo sobre la marcha. Tras esta experiencia me queda claro que todo el equipo debe conocer en cierta profundidad las tecnologías implicadas, especialmente Hibernate, incluso aspectos internos de funcionamiento. Es deseable que cada proyecto cuente con al menos una persona que realmente lo domine. Durante un desarrollo aparecen problemas complejos, no sólo errores, que no tienen una respuesta sencilla. Es fundamental contar con la capacidad de afrontarlos con solvencia. No sólo hablamos de problemas técnicos, sino también problemas de concepto. No podemos aumentar la produtividad sin aumentar la complejidad, aunque sí se puede reducir el coste. Utilizar herramientas avanzadas es la principal razón de aumento de la complejidad, y no se puede suponer símplemente que adoptando nuevas tecnologías se va a mejorar el proceso. Esta adopción requiere una gestión, especialmente en formación, y tiene un coste inicial. Y en este caso no basta con aprender sobre la marcha. Eso no es productivo. Es imprescindible pararase a aprender inicialmente antes de empezar a desarrollar.

Hibernate

Hibernate me sigue pareciendo sencillamente impresionante. Como con cualquier otra tecnología hay un coste de aprendizaje y todavía tiene algunos bugs y problemas, pero sigo opinando que no hay razón para no usarlo. Es una tecnología ya madura y contrastada.
Al emplear Hibernate hay dos problemas a solucionar: la formación del personal y problemas de integración con el SGBD específico. El primer punto está ya cubierto en el apartado anterior. Sobre el segundo, surgen problemas en la combinación de Hibernate y Oracle. El driver de Oracle, según los desarrolladores de Hibernate, está plagado de bugs. Es especialmente conflictiva la gestión de binarios (blobs, clobs...). Recomendaciones al respecto:
  • Lo primero es fijar el driver de Oracle. Localizar cuál es el que funciona en nuestro entorno (en los foros de Hibernate se habla al respecto) y agarrarnos a él sin soltarlo. Nada de actualizarlo.
  • Para solucionar los problemas con los blobs, Spring aporta el OracleLobHandler que te hace olvidar los bugs que aparecen al combinar Oracle con Hibernate.
Otras sugerencias aprendidas:
  • Usa Hibernate Validator para todas las validaciones de usuario. ¡DRY!
  • Usa Hibernate Annotations para configurar el mapeo relacional, mucho más cómodo que la configuración XML.
  • No te intentes adherir a EJB 3.0. Tiene limitaciones (consulta la documentación de Hibernate Annotations) con las que tarde o temprano te vas a encontrar.
Por último, en mi opinión, al contar con una herramienta así merece la pena cambiar la forma de afrontar la creación del modelo de datos. Tradicionalmente, el analista debe crear el modelo de datos para soportar los datos, el cual será transformado por el diseñador en un modelo físico de datos, un modelo de tablas y relaciones entre ellas que utilizará la aplicación. Sin embargo, esto ni es cómodo ni encaja bien a la hora de trabajar con Hibernate. Aunque los puristas supongo que me desollarán por lo siguiente, creo que hay que olvidar la creación del modelo de datos y pasar diréctamente al modelo de clases. No hagas diagramas. Haz diréctamente clases, y anótalas con @Entity, Hibernate hará el resto. Si puedes, utiliza claves surrogadas en vez de complejas claves compuestas. Con esto no quiero decir que no haya que tener en cuenta principios semejantes a los que se usan al crear el esquema de datos. Símplemente veo más operativo generar el esquema a partir de las clases en vez de trabajar en hacer el esquema y después verse encorsetado por él a la hora de programar.

Spring

No recuerdo ni un sólo problema real con Spring. Así como nuestra "guía de errores y excepciones" tiene infinidad de entradas en el apartado de Hibernate, ya que la transición de hacer SQL a mano a pasar a Hibernate es bastante compleja, la de Spring creo que está vacía. Usamos el módulo de IoC, el de seguridad, la gestión transaccional (programáticamente), y los añadidos que tiene para integración de otras librerías (Hibernate, servicios web...), al menos explícitamente. Ni siquiera la formación supuso un problema, ya que una vez se tiene organizado y configurado, basta con saber añadir e inyectar los beans en los xml para usarlo.
Es cómodo, útil, completo y maduro. Premia buenas prácticas y además aporta valor añadido, como publicación de servicios, inicialización de beans o planificación de tareas (mediante integración con librerías externas). Como queda claro, apuesta por la integración (lo cual queda claro cuando ves sus dependencias).
Lo único que lamento es la deficiente integración en el IDE (autocompletar es mejorable, escasa detección de errores) y que exija reiniciar el servidor al hacer cambios (cosa que tampoco ocurre tan a menudo).

JSF

Este no es el momento para criticar JSF, pero es imprescindible hacer una puntualización: JSF no es un framework completo, sólo es una base (mejorable) sobre la que usar otras cosas. Si pretendes usar sólo lo estándar, no estás comprendiendo su finalidad.
Optamos por MyFaces + Tomahawk + Sandbox + RichFaces + Facelets. Hacer funcionar todo esto sobre SJSAS 8.x no es una tarea trivial. 
  • Sobre la versión 8 del servidor no se puede usar JSF 1.2, estamos limitados a JSF 1.1.
  • El servidor de aplicaciones trae su propia implementación, y no usa la de la aplicación símplemente con incluirla. En el wiki de MyFaces hay más información al respecto.
  • Hacer componentes con "trozos de página", algo utilísimo para reutilizar código a nivel de vista, sólo con JSF es una tarea hercúlea. Como mínimo, tres clases más la configuración en el faces-config. Para mitigar esto hay que usar Facelets (imprescindible, creedme).
  • Facelets exige tener un taglib de las librerías usadas, no basta con el tld que ya tienen. De las librerías open source habituales hay taglibs disponibles, pero tenemos alguna librería propietaria que no lo incluye.
  • Nula gestión de excepciones.
  • Los errores a veces son crípticos. Facelets mejora algo esto, pero tampoco demasiado. Además, a veces aparecen problemas rarísimos de identificadores duplicados, solicitudes que se repiten, etc., sobre los que no hay respuesta clara.
  • Incompatibilidad entre navegadores. Lo habitual es trabajar en Firefox (por FireBugs y Web Developer Toolbar) y probar después sobre Internet Explorer, y es raro no llevarse sorpresas.
  • Olvídate de los editores gráficos. No merecen la pena. Ni para los ficheros de configuración, ni para las páginas, ni para nada. Codifica. Escribe cada línea de tu código, no pierdas el tiempo con vendedores de humo. Deja los editores gráficos para los diseñadores, los desarrolladores debemos manejarnos entre código.
Aún con todo esto, RichFaces me parece una buena razón para usar JSF. Uno de los objetivos del cambio es incorporar Ajax al interfaz, y la forma en la que afronta esta librería este problema es muy buena, la mejor posible en JSF. Sus componentes son buenos, pero ya sólo el a4j:support justifica su elección. Permite añadir Ajax a cualquier página JSF existente. "It just works". Nos hemos ido encontrando con bugs y problemas, pero el trabajo que se está haciendo en ella es muy bueno, y el soporte en el foro, también. Además, perfectamente documentado (en amplitud y profundidad), no como Dojo y otras tantas. Áltamente recomendable.

Pruebas

Las pruebas de una aplicación web de gestión tienen varios problemas intrínsecos: el estado de la base de datos, el perfil del usuario en sesión, y probar el interfaz.

¿Cómo podemos probar si una búsqueda filtrada paginar funciona si no sabemos si hay datos? La solución que hemos adoptado en estos casos es combinada. Por un lado, podemos consultar el estado actual de la base de datos. Por otro, modificamos su estado para hacer la prueba. Por ejemplo, ¿cómo saber si funciona el listado de 'tareas pendientes'? Primero consultamos las tareas pendientes de un usuario. Después hacemos una inserción que genere una tarea pendiente. Después hacemos otra consulta y comprobamos que hay una más de las que había antes. Por último, limpiamos lo insertado. Esto tiene dos problemas (al menos). La prueba es más compleja, aunque realmente no es un problema, porque como haremos una prueba de búsqueda y otra de inserción no tenemos más que programar una tercera que combine ambas, por lo que el coste de programar las pruebas apenas aumenta. Sin embargo, al hacerlo estamos metiendo una gran dependencia entre las pruebas, y eso es incorrecto.

Con el perfil del usuario el problema es similar. A menudo estamos obligados a asegurarnos de que existen usuarios con el perfil deseado, lo cual exige más control sobre el estado de la base de datos.

Tengo pendiente estudiar soluciones a este problema. Creo que TestNG y/o Unitils incorporan soluciones a esto, pero no he podido probarlo. De todas formas, siempre tendremos que especificar un estado concreto, con lo cual no estamos probando el sistema en cualquier caso, sino sólo en el especificado. Esto nos lleva a la discusión de cuánto probar, en la que no entraré en este momento :).

Para probar el interfaz por aquél entonces había alguna alternativa, pero demasiado costosas para mi gusto. BadBoy y Selenium añaden funcionalidad de prueba al navegador, pero preparar las pruebas sigue siendo costoso. Reciéntemente ha aparecido JSFUnit, que todavía no he podido probar.




Hasta aquí la exposición de los hechos de los últimos años. Ha acabado siendo un conglomerado de problemas, apaños (¿por qué usar el término 'workaround' teniendo alternativas propias?), opiniones y decisiones, pero en el fondo es de lo que se compone la experiencia diaria. Próximamente, las conclusiones, y seguro que otros posts que amplíen problemas aquí enunciados.

Espero con avidez los comentarios y críticas sobre todo esto. Estos dos años están siendo un proceso de aprendizaje del que esta entrada es sólo un paso más.

Posted by Juan Ignacio Sánchez Lara 16:04  

3 Comments:

  1. Alberto said...
    Totalmente de acuerdo en lo que dices. Mi experiencia concuerda bastante con la tuya.

    Sobre el comentario de que JSF es una base sobre la que usar otras cosas, una opción muy interesante es combinarla con Spring WebFlow. Realmente resuelve muchos problemas para gestionar la navegación y el estado, que en JSF puro están un poco flojos (soporte para conversaciones p. ej.)
    Nacho said...
    Estoy sopesando usar Seam para las limitaciones de JSF, y, sobre todo, para obtener en un paquete integrado las otras tecnologías que quiero usar (RichFaces, Hibernate con EntityManager, Validator, Annotations y Search, JBPM, Drools...)
    Joserra said...
    Absolutamente de acuerdo con: hay que olvidar la creación del modelo de datos y pasar diréctamente al modelo de clases.
    Es que si diseñamos con orentacion a objetos... y esos objetos pasan a ser persistentes, ¿que más te da cómo? (vale,a veces necesitas saber las tablas, pero solo a veces)...
    Claro, que luego te piden el modelo de datos... les das el UML del modelo de clases... y te dicen que qué c&%$ es eso...

Post a Comment