viernes, 19 de agosto de 2011

Motivos para elegir el framework Apache Tapestry

Apache Tapestry
En el mundo del desarrollo web en el entorno Java hay muchos frameworks disponibles en los que elegir (JSP/Servlets, Struts, Grails, Wicket, Play, Seam, ... Tapestry y varios más), a veces la elección del framework más adecuado para el desarrollo del proyecto que tenemos por delante no suele ser una decisión sencilla y no solo intervienen decisiones técnicas sino también los conocimientos y predisposición de aprender que tenga el equipo que vaya a desarrollarlo. Lo que está claro es que si el proyecto es más o menos complejo o va a necesitar mantenimiento nadie se plantea desarrollarlo directamente con JSP y Servlets.

En la siguiente entrada voy a tratar de explicar algunas características y dar algunos motivos y razones por los que deberías tener en cuenta el framework Apache Tapestry.

Componentes
La característica estrella para mi de este framework es la alta productividad y la reutilización de código que se puede conseguir con los componentes que se desarrollan en el framework ya que una vez desarrollados pueden ser usados tantas veces como se quiera a lo largo de todo el proyecto sin tener que conocer los detalles de como y que necesitan para funcionar salvo por los parámetros que recibe. Esta abstracción incluye los estilos y el javascript y Tapestry se encargará de agregar e incluir únicamente los necesarios en la página según la definición de los mismos.

Prácticamente en todos los proyectos hay necesidad de desarrollar una serie de funcionalidades o componentes comunes que son usados repetidas veces en el mismo o en diferentes proyectos. ¿Cuantas veces en un mismo proyecto has tenido que implementar la misma funcionalidad en páginas diferentes? ¿y cuantas veces te has tenido que pelear con sus parámetros que hay que recoger de la request en forma cruda (String) necesitando convertir su tipo correcto o transformarlo a las entidades de dominio? ¿y si tienes que hacer la misma funcionalidad en varios sitios de la misma aplicación, copias y pegas? ¿y si tienes que cambiar algo común lo tienes que hacer en todos los sitios? ¿cuanto código duplicado tienes? Está claro que tener código duplicado desperdigado en nuestra aplicación a la larga será un gran problema en el mantenimiento, pues bien, en con este framework el componente será solo uno y si tienes que hacer un cambio solo necesitarás modificarlo en un sitio. Solo tienes que pensar en cual es la funcionalidad que quieres reutilizar (en la misma o diferentes páginas) y meterla en un componente. El desarrollo orientado a componentes predispone para reutilizar código.

En frameworks como Struts y Grails se trabaja en términos de URL y parámetros en Tapestry en términos de componentes y objetos. Tapestry se encarga de convertir los parmámetros de la URL al tipo adecuado y de dejarlo en una propiedad del componente de ese tipo. Tapestry puede transformar un String a un tipo de objeto en concreto (por ejemplo una clase de dominio) y viceversa, esto es necesario ya que todo lo que se envía al navegador del usuario ha de ser un String ¿quien realiza esa transformación del objeto a String? ¿y de String al objeto cuando se vuelvan a enviar los datos al servidor? Todo esto lo hace a través de una clase que implementa la interfaz Translator o ValueEncoder.

También, cuando pasamos un parámetro a un componente es capaz de hacer las conversiones necesarias para transformar el tipo del parámetro que pasamos al tipo de parámetro del componente. En el gráfico de la documentación de conversiones se pueden ver las que hace se hacer por defecto aunque podríamos implementar una nueva si tuviesemos necesidad.

Además los componentes pueden ser empaquetados en una librería (un archivo .jar) y ser reutilizados en varios proyectos cosa que hará que el tiempo de desarrollo de los siguientes proyectos se reduzca bastante si las necesidades son parecidas. También a medida que vayamos tienendo una librería de componentes seremos más productivos en el mismo proyecto si es de larga duración o tiene mucho mantenimiento.

Pero tampoco hay que desarrollar los componentes desde cero, Tapestry ya proporciona una amplia colección de ellos listos para usar directamente o agregándolos en los nuevos que creemos y hay varias librerías de ellos desarrolladas en otros proyectos. Hay componentes para tratar campos de formularios, que permiten evaluar una condición, para recorrer una colección de elementos, para crear enlaces, para mostrar errores... también hay otros tan complejos como un grid para mostrar datos en una tabla paginada, para editar un bean o para cargar contenido en las páginas a través de AJAX y algunos otros para otras tareas.

En el componente grid que proporciona unicamente tendrás que implementar el método que será el que recupere los objetos de la base de datos ya que del resto se encargará el componente llamando a nuestro método de búsqueda con los parámetros necesarios para realizarla, como la página a mostrar. Pero hacer un nuevo componente no es nada complejo, cada componente está formado de un controlador (que es una simple clase Java POJO) y normalmente una platilla que es XHTML válido.

Otra características importante es que los componentes pueden lanzar eventos que pueden ser procesados por aquellos que los contienen, pudiendo de esta forma realizar diferentes acciones y comportamientos dependiendo del componente en el que está contenido. Esto añade más posibilidades de reutilización de los componentes.

Si desarrollamos muchos proyectos pequeños y que no tienen mucho mantenimiento podemos reutilizar los componentes que desarrollamos en un proyecto en otro, si desarrollamos uno, dos o pocos proyectos pero que tienen un gran mantenimiento podemos reutilizarlos igualmente en las funcionalidades comunes. En ambos casos de tipos de proyectos nos beneficiaremos enormemente de los componentes.

Modular y contenedor IOC integrado
Práctiamente cada funcionalidad de Tapestry puede ser modificada o extendida a través de su contenedor de inversión de control, solo hace falta hacer la contribución adecuada en el contenedor. Además, esas contribuciones no se hace en un XML sino que se hace a través de código Java con las ventajas que ello conlleva como la asistencia del compilador y la refactorización del IDE. El contenedor IOC implementa usa y pone a nuestra disposición varios de los patrones más usados como Chain of Command, Strategy Builder, Chain Builder, Shadow Builder, Pipelines, etc... Finalmente no impone un determinado framework de persistencia, de testing, de seguridad u otra cosa por lo que tendremos libertad de elegir la que más adecuado nos parezca en cada ámbito a resolver. En un mundo tan cambiante como la tecnología cuando surja esa otra nueva y brillante forma de hacer las cosas no nos veremos encadenados a una en concreto porque Tapestry nos lo exija y eso tarde o temprano pasará. ¿Quizá dentro de poco pase con hibernate y activejdbc? ¿Te gustaría estar encadenado a un framework que te imponga un modelo o framework de persistencia en concreto?

Hay plugins para las herramentas más habituales como Hibernate, String, Quartz, JFreeChart, Lucene, ... y si no existe siempre se puede hacer una integración definiendo un nuevo servicio. Como he dicho prácticamente cualquier apartado puede ser extendido o adaptado a nuestras necesidades. ¿Necesitas un validador o un objeto que convieta de un tipo a otro? No hay problema, implementa en un objeto la interfaz adecuada, configuralo en la aplicación y él se encarga de usarlo cuando sea necesario.

Tapestry no es un framework «full stack» pero eso... ¿es un defecto o una ventaja? Dependiendo del tipo de proyecto, nuestras preferencias o necesidades puede ser una cosa u otra.

Exception reporting
Otra buena característica de Tapestry es que cuando algo va mal no solo nos da la traza de la excepción que se ha producido en la consola del servidor sino que nos muestra una página con toda la información de la aplicación y de la petición realizada y que ha fallado, con lo que descubrir la causa de la excepción nos será más sencillo. ¿Te has exasperado alguna vez porque la línea que se indica en un error no se corresponde con la realidad? En el informe verás la linea exacta que ha producido la excepción y un extracto del código fuente de los componentes y los usos de unos a otros hasta llegar a la excepción, además en la traza el informe te marcará en otro color las clases que se han usado de tu aplicación. Por supuesto está pagina puede ser personalizada en un entorno de producción. En Tapestry 5.3 se ha incorporado un informe incluso para las peticiones AJAX y el autor del framework ha publicado un screencast en el que se pueden ver los informes de errores en acción.

Productividad
En caso de hacer modificaciones en las clases de los componentes, páginas y servicios o a las plantillas de los mismos los cambios se hacen efectivos inmediatamente sin tener que realizar un redespliegue de la aplicación aunque en algunos casos no nos quede más remedio que hacerlo. Aún en ese caso la aplicación tarda poco en arrancar comparado con otros frameworks. Si desarrollamos en Java podemos aprovecharnos del soporte de refactor que ofrecen los IDE. La productividad se consigue reutilizando componentes ya desarrollados y probados.

Rendimiento y escalabilidad
En las últimas versiones se han incorporado características para aumentar el rendimiento y su escalabilidad como la eliminación del pool de páginas, ahora solo hay una instancia para todas las peticiones de la aplicación, la eliminación de espacios en blanco innecesarios lo que reduce el tamaño del contenido a devolver, la compresión del contenido devuelto para reducirlo aún más y la agregación de javascripts y hojas de estilo para reducir el número de peticiones a realizar al servidor. Con estas características se consigue que la página carge más rápido en el cliente y disminuya la carga de trabajo en el servidor al realizarse menos peticiones.

Soporte localización
En otros frameworks es habitual que solo exista un bundle con las traducciones de toda la aplicación. Tapestry permite una granularidad mayor, los componentes pueden tener su propio bundle de propeties con las traducciones específicas de ese componente, además los componentes y páginas pueden tener una plantilla diferente según el locale (y a partir de la versión 5.3 podriamos tener una plantilla diferente si el cliente es un dispositivo movil u otras características que decidamos) e incluso las imágenes con textos son recursos que podrían localizarse eligiendo la adecuada según el lenguaje de la aplicación.

Validaciones
La validación de los datos de entrada de un usuario de la aplicación es una de las cosas importantes que como desarrolladores tenemos que tratar. La validaciones son muy simples de aplicar ya que se hace de forma declarativa dejando a Tapestry todo el trabajo de comprobarlas en el servidor. Además es capaz de presentar los errores al usuario en caso de que alguna haya producido algún error. Soporta la especificación JSR-303.

Políglota
¿Te gustan otros lenguajes que han aparecido recientemente y que se pueden ejecutar sobre la Java VM como Groovy o Scala? Pues con él puedes desarrollar tus componentes, páginas y servicios con el lenguaje que prefieras ya sea Java, Groovy, Scala o cualquier otro que se pueda ejecutar sobre la máquina virtual. Puedes ver un ejemplo en otra entrada que he escrito Usar Apache Tapestry 5 con Groovy (u otros lenguajes de la JVM).

Otras características
Tapestry implementa el patrón «redirect after post» que resuelve algunos problemas inherentes a las aplicaciones web o los botones atrás y adelante de los navegadores. En un artículo de theserverside, Redirect After Post, tienes una explicación más detallada de este patrón.

Además de las anteriores, y en mi opinión personal, otra es que con él realmente disfruto programando y quedo satisfecho de como queda el trabajo que realizo. ¿Se podría decir algo mejor de un framework de forma resumida?

Por supuesto no es perfecto y estas  son solo unas pocas de sus características pero creo que en los aspectos importantes como la reutilización de código en base a los componentes, su modularidad, adaptabilidad y gran rendimiento, productividad y su desarrollo sencillo una vez aprendidos sus conceptos básicos considero que gana por mucha diferencia respecto a otros frameworks. Una de sus deficiencias respecto a otros frameworks (o no) como Grails y otros parecidos en otros lenguajes (PHP, Ruby) es que no proporciona un entorno tan integrado («full stack») como la posibilidad de ejecutar los teses con grails test-app o de ejecutar la aplicación grails run-app, pero esto también tiene la ventaja de que no se nos impone uno y podemos usar el que queramos y tenemos mayor control sobre lo que hacemos.

La versión actual de Tapestry es la 5.2.6 estando al versión 5.3 en estos momentos en proceso de entrar en estado beta. Los cambios que incorporará 5.3 pueden consultarse en las notas de publicación de la versión.

Si tienes curiosidad de ver algo de código de una aplicación puedes ver estas entradas: Hola Mundo con Tapestry 5, Componentes lista paginada con Tapestry y si quieres ver una aplicación funcionando puedes acceder a este Hola Mundo con Tapestry 5 que se está ejecutando en el Google App Engine. También en la aplicación JumpStart tienes un montón de ejemplos  interesantes con el código fuente sobre diversas características de él, seguro te sorprende lo simple y «limpio» que es el código para las cosas que se hacen en esos ejemplos.

En la entrada Documentación sobre Tapestry puedes ver la documentación disponible que he ido recopilando por la red.

Espero que estos motivos os animen a echarle un vistazo, con que sólo uno lo hiciera ya me sentiría recompensado por haber escrito la entrada. Si es así pueden estar atentos a las entradas que escriba en mi blog ya que pretendo seguir escribiendo sobre Tapestry en él :).

Referencia:
http://mail-archives.apache.org/mod_mbox/tapestry-users/201201.mbox/%3C4F210FCA.3020801@ictjob.be%3E

jueves, 11 de agosto de 2011

Seleccionar el lenguaje (locale) según el dominio en Apache Tapestry

Apache Tapestry
Apache Tapestry por defecto determina el lenguaje (locale) de la aplicación por los headers que envía el navegador del usuario con sus preferencias. Si tenemos un sitio internacional con varios dominios y soportando varios lenguajes para cada país o dominio quizá nos interese más determinar el idioma del usuario por el dominio al que accede. Una de las cosas excelentes de Tapestry es que es extremadamente modular y extensible y cualquier cosa que es definida como un servicio puede ser adaptada a nuestras necesidades a través del contenedor de dependencias (IOC).

Para conseguir lo que queremos el servicio que debemos redefinir es org.apache.tapestry5.services.LocalizationSetter que es el responsable de establecer el locale según las preferencias enviadas por el usuario en la petición. Por tanto debemos desarrollar una clase con la lógica que necesitamos y que implemente esa interfaz. Pero no hace falta que la desarrollemos desde cero, podemos partir de la que usa Tapestry internamente org.apache.tapestry5.internal.services.LocalizationSetterImpl (el software libre/código abierto es genial ¿verdad?).

La parte interesante e importante que debemos redefinir es la siguiente:

	/* AppLocalizationSetter.java */
	...
	private final Request request;
                private final PersistentLocale persistentLocale;
        	private final ThreadLocale threadLocale;

                private Map<String, String> defaultLocales;
                ...
                public AppLocalizationSetter(Request request, PersistentLocale persistentLocale, ThreadLocale threadLocale, @Symbol(SymbolConstants.SUPPORTED_LOCALES) String localeNames, ...) {
                        ...
                        this.defaultLocales = new HashMap<String, String>();
                }
	...
	public boolean setLocaleFromLocaleName(String localeName) {
		boolean supported = isSupportedLocaleName(localeName);
		
		Locale supportedLocale = findClosestSupportedLocale(localeName);
		if (!localeName.trim().equals("")) {
			persistentLocale.set(supportedLocale);		
		}

		threadLocale.setLocale(supportedLocale);

		return supported;
        }
        ...
	private Locale findClosestSupportedLocale(String desiredLocale) {
		String localeName = desiredLocale;

		while (true) {
			if (isSupportedLocaleName(localeName))
				return toLocale(localeName);

			localeName = stripTerm(localeName);

			if (localeName.length() == 0)
				break;
		}
               return getDefaultLocale(desiredLocale);
	}

	private String getDomainLocale() {
		String dominio = request.getServerName();
		if (defaultLocales.get(dominio) != null) {
			return defaultLocales.get(dominio);
		}
		// Buscar el idioma según el dominio
                // ¿en una constante?
                // ¿quizá en base de datos?
		String locale = ...;
		defaultLocales.put(dominio, locale);
                return locale;
	}

	private Locale getDefaultLocale(String desiredLocale) {
                String domainLocale = getDomainLocale();
        
                if (domainLocale.equals(desiredLocale)) {
                        // El locale del dominio no está soportado
            	        return supportedLocales.get(0);
                }
        
                Locale defaultLocale = findClosestSupportedLocale(domainLocale);
        
                return defaultLocale;
	}
	...

	/* AppModule.java */
	...
	public static void bind(ServiceBinder binder) {
		binder.bind(LocalizationSetter.class, AppLocalizationSetter.class).withId("AppLocalizationSetter");
	}
	
	public static void contributeServiceOverride(MappedConfiguration configuration, @Local LocalizationSetter override) {
		configuration.add(LocalizationSetter.class, override);
	}
	...


Lo importante a modificar es la forma de encontrar un locale soportado dado un localeName e implementar el método getDomainLocale() que tendrá nuestra lógica, obteniendo el Locale a partir de dominio por el que se ha accedido a la aplicación («request.getServerName()»). La forma de hacer la correspondencia entre el dominio y Locale la podemos hacer de la mejor forma que se nos ocurra de forma simple con un mapa con los dominios como claves y los locales como valor o accediendo a una base de datos que tenga la correspondencia.

Tapestry codifica en las URLs el idioma del usuario para hacerlo persistente, esto se hace con persistentLocale.set(locale). Si el locale que se intenta establecer está soportado por la aplicación se hacer persistente para posteriores peticiones y se hace efectivo para la petición actual. Si el locale que se intenta establecer no está directamente soportado se busca uno más general soportado, esto es lo que hace el método findClosestSupportedLocale. Por ejemplo, si se intenta usar el locale «en_US» y nuestra aplicación no lo soporta pero si soporta «en» se usa este último. Finalmente, si no encuentra ninguno devuelve el del dominio con la lógica que hemos definido y si tampoco es uno de los soportados de entre los de la lista del símbolo SymbolConstants.SUPPORTED_LOCALES finalmente se usa el del defecto de la aplicación (el primero de esa lista).

Deberemos hacer las contribuciones de nuestra aplicación en AppModule.java que sobreescriben el sevicio usado por defecto en Tapestry por el sevicio con nuestra lógica.

Tapestry tiene un gran diseño y es muy flexible y adaptable, este es un ejemplo, redefiinimos el comportamiento con una nueva lógica que está localizada en una clase y puede ser fácilmente redefinida por otra en el momento que tengamos necesidad sin tener que cambiar nada de código en el resto de la aplicación salvo en el AppModule.

Referencia:
Documentación sobre Apache Tapestry

viernes, 5 de agosto de 2011

Internacionalización a lenguajes con diferentes formas plurales en Java


Java
Si desarrollamos una aplicación para diferentes lenguajes y por tanto necesitamos internacionalizarla es probable que tengamos que tener en cuenta el número de diferentes formas plurales que tengan esos lenguajes. El español tiene dos formas plurales o tipos de número (singular/1, plural/1+) pero otros lenguajes tienen más de dos formas plurales (nular/0, dual/2, trial/3, paucal/grupo reducido). Java da soporte a esta funcionalidad a través de la clase ChioceFormat aunque nos requerirá un poco más de trabajo formatear cada mensaje.

A través de la clase ChoiceFormat elegimos la cadena con la forma plural correcta, posteriormente tal vez debamos hacer sustituciones en el mensaje con un MessageFormat.

En el javadoc de ChoiceFormat viene explicado como construir un patrón para posteriormente con la cantidad de elementos seleccionar la forma plural correcta.

Estos dos pasos quedarían así:

import java.text.ChoiceFormat;
import java.text.MessageFormat;

public class Test {
    public static void main(String[] args) {
	// Construir el ChoiceFormat
        ChoiceFormat cf = new ChoiceFormat("0#Hay {0} elementos.|1#Hay {0} elemento.|1<Hay {0} elementos.");
        String m = null;
        double n = 0;

        // Obtener la forma plural
        n = -3;
        m = cf.format(n)

        // Formatear
        System.out.println(MessageFormat.format(m, n));

        n = 0.5;
        m = cf.format(n)
        System.out.println(MessageFormat.format(m, n));
        
        n = 1;
        m = cf.format(n)
        System.out.println(MessageFormat.format(m, n));

        n = 2.5;
        m = cf.format(n)
        System.out.println(MessageFormat.format(m, n));

        n = 3;
        m = cf.format(n)
        System.out.println(MessageFormat.format(m, n));
    }
}

El resultado de este programa es:

Hay -3 elementos.
Hay 0,5 elementos.
Hay 1 elemento.
Hay 2,5 elementos.
Hay 3 elementos.

Si el String que se le pasa al ChoiceFormat viniese de un archivo .properties con los mensajes de cada idioma podríamos soportar varias formas plurales cambiando los límites en el mensaje localizado. En español tendríamos un literal en un properties tal que:

elementos=0#Hay cero elementos.|1#Hay un elemento.|1<Hay algo más de un elemento.

Pero en otro idioma podríamos tener esto según las diferentes formas plurales de ese idioma:

elementos=0#Hay menos de un elemento.|1#Hay un elemento.|1<Hay algo más de un elemento.|2#Hay dos elementos||2#Hay algo más de dos elementos.|3#Hay tres o más elementos.

Hay menos de un elemento.
Hay menos de un elemento.
Hay un elemento.
Hay algo más de dos elementos.
Hay tres o más elementos.


Referencia:
http://download.oracle.com/javase/7/docs/api/java/text/ChoiceFormat.html
http://download.oracle.com/javase/7/docs/api/java/text/MessageFormat.html