Archivo de la categoría: Programming

Arquillian y GlassFish: portar ‘Hola Mundo’ desde TomEE

Por ahora hemos visto cómo efectuar un test Hola Mundo con Arquillian y WildFly, y cómo portar dicho test a Arquillian + TomEE.

Hoy veremos cómo ejecutar ese test contra GlassFish, usando su adaptador managed.

1.Configuración de dependencias

En teoría, lo único que debería cambiar a nivel de dependencias son las librerías de arquillian para GlassFish (cambio #1).

Modificaremos build.gradle para utilizar org.jboss.arquillian.container:arquillian-glassfish-managed-3.1:1.0.0.Final-SNAPSHOT, como sigue:

   // *****************************************************************************
   // GlassFish managed
   testRuntime 'org.jboss.arquillian.container:arquillian-glassfish-managed-3.1:1.0.0.Final-SNAPSHOT'

2. Código

El mismo que para WildFly y para TomEE.

3. Configuración de Runtime

Aunque tocaría ahora, ya hemos cambiado las dependencias de tiempo de ejecución para tests más arriba.

Debemos cambiar el archivo arquillian.xml para configurar el servidor managed de GlassFish (cambio #2), que quedará como sigue:

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://jboss.org/schema/arquillian
        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
   <container qualifier="glassfish-managed" default="true">
      <configuration>
         <property name="glassFishHome">/home/pagdev/pagde/tool/glassfish-4.1/</property>
      </configuration>
   </container>
</arquillian>

La única propiedad obligatoria de esta configuración es glassFishHome, que debe apuntar al directorio de instalación de GlassFish.

4. Ejecutar tests

Se ejecutan exactamente igual:

gradle test --tests *Test

Notas

Tan solo hemos tenido que hacer dos cambios para poder lanzar el test más simple posible usando un bean EJB.

Gradle tardó en ejecutar el test contra GlassFish managed alrededor de 18 segundos, y contra WildFly managed sobre los 11 segundos, y a lo largo de numerosas ejecuciones los tiempor variaron no más de 2 segundos. Contra Tomcat embedded, los tests se ejecutaron en torno a 6 o 7 segundos.

Software utilizado

Esto ha sido probado con GlassFish 4.1 y Arquillian 1.1.8, usando Gradle 2.3.

Anuncios

Arquillian y TomEE: portar ‘Hola Mundo’ de WildFly a TomEE

En teoría, Arquillian permite escribir tests de integración para varios servidores utilizando el mismo código y con unos cambios mínimos de configuración.

En este blog mi objetivo es usar Arquillian con TomEE en lugar de WildFly, y ver qué cambios debemos realizar para ejecutar el mismo test Hola Mundo que ya vimos en WildFly.

El objetivo es detallar todos y cada uno los cambios de configuración entre TomEE embedded y WildFly embedded.

1.Configuración de dependencias

Dado que no usamos ninguna funcionalidad específica de ningún servidor de aplicaciones, las dependencias de compilación y de test son las mismas que en el proyecto para WildFly.

En teoría, lo único que debería cambiar a nivel de dependencias son las librerías del runtime de Tomee (cambio #1), necesarias para arrancar y ejecutar TomEE en lugar de WildFly.

Modificaremos build.gradle para utilizar org.apache.openejb:arquillian-tomee-embedded:1.7.1, como sigue:

   // *****************************************************************************
   // TomeePlume: embedded
   testRuntime 'org.apache.openejb:arquillian-tomee-embedded:1.7.1'

Sin embargo, en la práctica, al ejecutar los test, nos encontramos con un error, java.lang.ClassFormatError, que se soluciona cambiando la dependencia de la API de JEE6 a javax:javaee-api:6.0 (cambio #2) para TomEE.

Como WildFly funciona perfectamente contra esta dependencia, este es más bien un cambio a hacer a la configuración de WildFly que a la de TomEE.

2. Código

El mismo que para WildFly.

3. Configuración de Runtime

Aunque tocaría ahora, ya hemos cambiado las dependencias para ejecutar TomEE en lugar de WildFly más arriba.

Por supuesto, debemos cambiar el archivo arquillian.xml para configurar el servidor embedded de Tomee (cambio #3), que quedará como sigue:

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://jboss.org/schema/arquillian
        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
   <container qualifier="tomee" default="true">
      <configuration>
      </configuration>
   </container>
</arquillian>

Y, sí, no hace falta configurar ningún parámetro para usar el TomEE embedded.

Otra cosa a tener en cuenta es que en la versión de WildFly con el adaptador embedded era obligatorio pasar a la máquina virtual del servidor el parámetro java.util.logging.manager, seleccionando el log manager de WildFly. Pero dicho gestor no está disponible en TomEE, por lo que no arrancará.

Solución: no pasar el parámetro java.util.logging.manager al runtime de TomEE (cambio #4).

4. Ejecutar tests

Se ejecutan exactamente igual.

En resumen

Hemos tenido que hacer cuatro cambios para poder lanzar el test más simple posible usando un bean EJB.

En realidad, si actualizamos la versión de WildFly para que use la dependencia org.apache.openejb:javaee-api:6.0-4 como API de JEE 6, con la que funciona perfectamente, el número de cambios es de 3, todos ellos triviales.

No está nada mal, ¿no?

Notas

Como nota aparte, comentar que Gradle tardó en ejecutar el test contra TomEE 5.856 segundos. El mismo test, lanzado contra WildFly con la configuración standalone.xml tardó 9.443 segundos.

Los tiempos en numerosas ejecuciones se mantuvieron siempre en torno a estos valores, con variaciones de no más de 1 segundo. Es una diferencia notable y que vale la pena mencionar, pero tampoco le daría mucha importancia, ya que el nuestro no es un escenario realista.

Software utilizado

Esto ha sido probado con TomeePlume 1.7.1 y Arquillian 1.1.8, usando Gradle 2.3.

Arquillian y WildFly: test ‘Hola Mundo’

Arquillian permite crear tests de integración con servidores/contenedores web y JEE como WildFly (o Tomcat), de forma bastante sencilla.

Para trabajar con un servidor, Arquillian usa adaptadores de contenedor, que permiten arrancar y parar los contenedores. Estos pueden ser de tres tipos: embebidos, gestionados, y remotos (embedded, managed y remote, a partir de ahora).

Para este Hola mundo usaremos el adaptador embedded, y dejaremos para otro artículo la discusión de qué ventajas e inconvenientes tiene cada tipo de adaptador y cuándo usar cada uno.

1. Configuración de dependencias

Para compilar los tests necesitaremos JUnit y Arquillian: con tan solo incluir junit:junit:4.11 y org.jboss.arquillian.junit:arquillian-junit-container:1.1.8.Final en build.gradle se incluirán todos los demás jars necesarios, especialment las dependencias de Arquillian.

Para ejecutar los tests, debemos incluir un adaptador de contenedor de WildFly, que Arquillian usará para arrancar y parar el contenedor contra el que se ejecutará el test. Como usaremos el contenedor embedded de WildFly, debemos incluir org.wildfly:wildfly-arquillian-container-embedded:8.2.0.Final en la lista de dependencias.

Por último, para que Gradle pueda encontrar algunas de estas dependencias deberemos añadir un repositorio de JBoss a nuestro build.gradle, que una vez hecho todo esto quedará como sigue:

apply plugin: 'java'

sourceCompatibility = 1.6

repositories {
  mavenCentral()
  maven {
    url "http://repository.jboss.org/nexus/content/groups/public-jboss"
  }
}

dependencies {
  compile 'javax:javaee-api:6.0'
  testCompile 'junit:junit:4.11',
    'org.jboss.arquillian.junit:arquillian-junit-container:1.1.8.Final'
  testRuntime 'org.wildfly:wildfly-arquillian-container-embedded:8.2.0.Final'
}

test {
  // Si no se especifica el log manager con el adaptador embedded de WildFly 8.2,
  // no arrancara correctamente
  systemProperties 'java.util.logging.manager': 'org.jboss.logmanager.LogManager'
}

2. Código

Para demostrar cómo implementar y ejecutar tests sencillos crearemos un stateless session bean, MyStatelessEjb, que simplemente suma dos números:

import javax.ejb.Stateless;

@Stateless
public class MyStatelessEjb {
   public int sum(int a, int b) {
      return a + b;
   }
}

Para hacer un test JUnit con Arquillian necesitaremos un runner de JUnit especial, incluido con Arquillian, para lo que anotaremos la clase de test con @RunWith(Arquillian.class).

También debemos especificar la configuración básica de la aplicación, para que el test se pueda ejecutar en el contexto adecuado. Debemos indicar dónde están los .class necesarios, si vamos a crear un war u otra cosa, quizá incluir un archivo de configuración como persistence.xml, etc.

Para ello se debe crear un método anotado con @Deployment, que utilizará la clase ShrinkWrap para llevar a cabo toda esta tarea de configuración.

El código resultante para hacer el test será como sigue:

@RunWith(Arquillian.class)
public class MyStatelessEjbTest {
   @EJB
   private MyStatelessEjb ejb;

   @Deployment
   public static Archive<?> createTestArchive() {
       return ShrinkWrap.create(WebArchive.class)
               .addPackage(MyStatelessEjb.class.getPackage());
   }

   @Test
   public void testSum() {
      Assert.assertEquals(5, ejb.sum(6,-1));
   }
}

Por lo demás, los tests son tests JUnit normales y corrientes, como se puede ver con testSum. Nótese que este test usa nuestro bean, que se ha inyectado en el test con @EJB, y que el propio contenedor WildFly gestiona.

3. Configuración de Runtime

Llegados a este punto, la aplicación compilará, pero aún es necesario dar unos pasos extra para que la aplicación arranque y podamos ejecutar los tests.

Las librerías adicionales necesarias en tiempo de ejecución ya las hemos incluido en un paso anterior, pero aún debemos configurar Arquillian. Esto se hace mediante arquillian.xml, que ubicaremos en el directorio src/test/resources del proyecto. Quedará como sigue:

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jboss.org/schema/arquillian
      http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
  <container qualifier="jboss-embedded" default="true">
    <configuration>
      <property name="jbossHome">/home/pagdev/pagde/tool/wildfly-8.2.0.Final</property>
      <property name="modulePath">/home/pagdev/pagde/tool/wildfly-8.2.0.Final/modules</property>
      <property name="serverConfig">standalone-full.xml</property>
    </configuration>
  </container>
</arquillian>

Aquí usamos la propiedad jbossHome, requerida, para indicar cuál es el directorio donde se halla JBoss (como JBOSS_HOME).

Esta versión del adaptador también requiere que se le pase el parámetro modulePath, que es donde se encuentran los módulos utilizables de JBoss. Por defecto están en el subdirectorio modules en JBoss. Es curioso que este parámetro sea obligatorio, dado que podrían utilizar un valor por defecto sin problemas.

También he añadido el parámetro serverConfig, que nos permite especificar qué configuración de servidor vamos a utilizar, y que no es requerido.

4. Ejecutar tests

Desafortunadamente necesitamos un pequeño workaround para un bug del adaptador embedded. Es necesario pasarle al contenedor WildFly arrancado por Arquillian el parámetro java.util.logging.manager, para el que simplemente utilizaremos el LogManager por defecto de WildFly. Esto es lo que hace el siguiente fragmento de build.gradle:


test {
  systemProperties 'java.util.logging.manager':
         'org.jboss.logmanager.LogManager'
}

Una alternativa a esto sería llamar a gradle desde la línea de comando pasándole ese parámetro. Para lanzar todos los tests del proyecto, por ejemplo, tendríamos la siguiente línea de comandos:

gradle test -Djava.util.logging.manager=org.jboss.logmanager.LogManager --tests *Test

Con la configuración actual de build.gradle, podemos lanzar el test con

gradle test --tests *Test

Por otro lado, si ejecutamos los tests desde Eclipse y no usando Gradle desde la línea de comandos, habrá que indicarle al launcher correspondiente que pase a la VM el parámetro -Djava.util.logging.manager=org.jboss.logmanager.LogManager.

Si queremos ejecutar estos tests desde la línea de comandos, y depurarlos con Eclipse, vale la pena consultar este otro artículo.

Software utilizado

Esto ha sido probado con WildFly 8.2 y Arquillian 1.1.8, usando Gradle 2.3.

Spring and JPA: configuring Spring programmatically for Hibernate, EclipseLink, OpenJpa and DataNucleus

Here is my take at configuring Spring programmatically to support the more popular JPA providers out there: Hibernate, EclipseLink, OpenJpa and DataNucleus.

I am assuming you already know how to programmatically configure a data source, as well as how to use the @Configuration annotation. I’m just providing the @Bean definition for the entity manager factory.

Shared code

The following bean definition code provides common code shared by all JPA providers, and then calls configureProvider: we will isolate all provider specific code in that method.

@Bean
public LocalContainerEntityManagerFactoryBean 
  entityManagerFactory() throws PropertyVetoException 
{
  LocalContainerEntityManagerFactoryBean result =
    new LocalContainerEntityManagerFactoryBean();

  // loadTimeWeaver will be available if 
  //   @EnableLoadTimeWeaving is specified in this 
  //   config class (annotated with @Configuration)
  result.setLoadTimeWeaver(loadTimeWeaver);

  // coreDs should be a DataSource bean, 
  //   configured elsewhere.
  result.setDataSource(coreDs);

  Properties jpaProperties = new Properties();
  // ** Configuration common to all JPA 2.0 managers
  // coreDsDriverName is the database driver name!
  //    used to configure the datasource AND the 
  //    JPA provider
  jpaProperties.put( "javax.persistence.jdbc.driver", 
                     coreDsDriverName);

  // ** Provider specific config isolated here
  configureProvider(jpaProperties, result);

  result.setJpaProperties(jpaProperties);
  result.afterPropertiesSet();
  return result;
}

With this configuration you will have to provide your persistence.xml file. Just do not put a <provider> entry there: it will not be needed, we are specifying the provider programmatically.

However, if you want to remove the persistence.xml file, you can do that by specifying the packages to scan for entities programmatically, calling result.setPackagesToScan.

EclipseLink configuration

This is my very basic EclipseLink configuration. You should add EclipseLink specific configuration properties to jpaProperties, and provide configuration data to the entity manager factory bean (emf) so that it can instantiate the right provider.

private void configureProvider(Properties jpaProperties, 
     LocalEntityManagerFactoryBean emf) {
  // We want EclipseLink to recreate the database schema
  jpaProperties.put("eclipselink.ddl-generation", 
                    "drop-and-create-tables");
  emf.setJpaVendorAdapter(
    new EclipseLinkJpaVendorAdapter());
}

Hibernate configuration

This is my very basic Hibernate configuration:

private void configureProvider(Properties jpaProperties, 
     LocalEntityManagerFactoryBean emf) {
  // We want Hibernate to recreate the database schema
  jpaProperties.put(
        org.hibernate.cfg.Environment.HBM2DDL_AUTO, 
        "create-drop");
  // And we want Hibernate!
  emf.setJpaVendorAdapter(
     new HibernateJpaVendorAdapter());
}

OpenJpa configuration

OpenJpa basic configuration follows:

private void configureProvider(Properties jpaProperties, 
     LocalEntityManagerFactoryBean emf) {
  // We want EclipseLink to recreate the database schema
  jpaProperties.put("openjpa.jdbc.SynchronizeMappings", 
                    "buildSchema");
  jpaProperties.put("openjpa.InitializeEagerly", 
                    "true");

  emf.setJpaVendorAdapter(
     new OpenJpaVendorAdapter());
}

DataNucleus configuration

If you are using DataNucleus as your persistence provider, you are a bit out of luck, as Spring does not implement a vendor adapter for DataNucleus. We need to use a different approach, as follows.

private void configureProvider(Properties jpaProperties, 
     LocalEntityManagerFactoryBean emf) {
  // We want DataNucleus to recreate the database schema
  jpaProperties.put("datanucleus.autoCreateSchema", 
                    "true");
  emf.setPersistenceProviderClass(
     org.datanucleus.api.jpa.PersistenceProviderImpl.class);
}

Thats’ it!

Cloud Foundry y Micro Cloud Foundry para desarrolladores Spring

Las últimas semanas he estado colaborando en un interesante proyecto, una prueba de concepto que incluía entre otras cosas el uso la infraestructura de Cloud Foundry para proyectos con Spring en el backend -parte de la que se ha encargado un servidor.

¿Qué es Cloud Foundry, sin marketing y para un desarrollador?

Cloud Foundry es una iniciativa detrás de la que está VMware como gran esponsor, y que ofrece soporte para ‘cloudificar’ nuestras aplicaciones con la consiguiente escalabilidad, estandarización de la infraestructuda y todo eso.

Dejando aparte los discursos de estrategia y márketing, y centrándonos en el impacto para los desarrolladores Spring, la cosa apenas se complica mucho por ello. Y esa es la gran virtud.

Cloud Foundry proporciona una infraestructura estable y estándar (p.ej. Java 1.6 o 1.7, tc Server/Tomcat, Spring, etc.) que incluye diversos servicios (MySql 5.1, Redis, etc.), contra la que podemos desarrollar y desplegar nuestras aplicaciones.

Para los desarrolladores existe la posibilidad de usar como sustituto de la cosa real una máquina virtual que proporciona toda la infraestructura y servicios de Cloud Foundry, y que podemos usar para desarrollar en local. Básicamente es a esto es a lo que se llama Micro Cloud Foundry, ni más ni menos.

Vale, pero ¿cómo afecta todo esto a mi código Spring? Pues en muchos casos el impacto es cero: por ejemplo, en nuestra aplicación se ataca una base de datos MySql, digamos ‘killer_demo’, con el usuario ‘myUser’ y la password ‘pwd’. ¿Qué cambios hice con respecto a una aplicación Spring a desplegar en nuestro propio entorno? Cero. En mi caso utilicé Spring beans para atacar MySql, usando la siguiente configuración

    <beans:bean id="springDataSource" 
     class="org.apache.commons.dbcp.BasicDataSource">
       <beans:property name="driverClassName" 
          value="com.mysql.jdbc.Driver" />
       <beans:property name="url" 
          value="jdbc:mysql://localhost/killer_demo" />
       <beans:property name="username" value="myUser" />
       <beans:property name="password" value="pwd" />
    </beans:bean>

    <beans:bean id="springEmf"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <beans:property name="persistenceUnitName" 
           value="killer_demo_pu"/>
        <beans:property name="persistenceProviderClass" 
           value="org.hibernate.ejb.HibernatePersistence"/>
        <beans:property name="dataSource" 
           ref="springDataSource"/>
    </beans:bean>

La cosa es que cuando enviamos la aplicación a un sistema que soporte Cloud Foundry, el correspondiente mecanismo automágico le echa un vistazo a los Spring beans y los reconfigura. Así, el sistema ve que tenemos una fuente de datos que utiliza un usuario ‘myUser’ y una contraseña ‘pwd’: si no existe una base de datos la crea automáticamente, con un nombre que se saca de la manga, y por supuesto trabaja con usuarios y contraseñas generados del mismo modo.

Y lógicamente sustituye los valores proporcionados por nosotros por los valores que se ha sacado de la manga en los propios beans.

Obviamente, si la configuración de la aplicación se complica o deseamos un mayor control, puede llegar a ser necesario dar información específica a Cloud Foundry, información que se proporcionará vía configuración de Spring. Aún así, el impacto en el resto del código fuente será cero.

Como os podéis imaginar, la cosa funciona de forma similar si en lugar de usar Java+Spring se usa otra infraestructura soportada, como Scala, Grails, Node.js, etc.

¿Y cómo configuro o despliego aplicaciones en Cloud Foundry?

Para tareas tales como desplegar y configurar la aplicación (¿cuánta memoria le vamos a dar?) y los servicios que usa, deberemos utilizar vmc o el correpondiente plugin de Eclipse.

Por ejemplo, para instalar myApp.war por primera vez, tendremos que ejecutar

vmc push myApp

O, para ver las aplicaciones instaladas, bastará con

vmc apps

Desde luego, puede ser necesario controlar el entorno de manera mucho más fina por parte de un administrador, y este puede obtener acceso a los distintos servicios mediante tunneling, algo que no tiene por qué afectar al código fuente.

Micro Cloud Foundry está muy bien, pero…

… eso de tener que enviar un war con los cambios mientras desarrollas no es que favorezca el desarrollo ágil, la verdad.

Personalmente prefiero desarrollar mis aplicaciones en local, dado que me permite un desarrollo mucho, mucho más ágil: si se hace con cuidado, se puede desarrollar sin problemas la aplicación contra un Tomcat local, una base de datos local y cualesquiera otros servicios locales, y luego moverla a Micro Cloud Foundry para verificar que no hemos hecho nada raro.

Un detalle puntual: dado que Micro Cloud Foundry instala nuestra aplicación como si fuera la ROOT de Tomcat, no importa cómo llamemos al WAR que desplegamos, será conveniente que configuremos nuestra aplicación y el Tomcat local para que tenga esto en cuenta: aquí hay información relevante al respecto.

Notes on using JSDuck for javascript API documentation

I’ve been using JSDuck to generate log4js-ext documentation, and I’m quite happy with it.

It allowed me to generate the log4js-ext API documentation in a day, and I had to learn how to use it and decide some baseline practices in that time.

These notes are relative to JSDuck 3.11.0, and include some practices, doubts and precautions I consider relevant.

BTW, the list of tags JSDuck supports is here.

Important practices -at least to me!

  • Data types: this is key, because javascript is not type safe and sometimes the type is not evident

    • Always specify the type for parameters, return values and properties.
      This is usually done writing {xxx} somewhere, where xxx is the type (String, Array, Sm.log.Logger,etc.).
    • If something can have several types, use this notation: {String/Sm.log.Logger}, with ‘/’ separating several alternative types.
    • If you want to refer to the special arguments variable, you can define its type as {Arguments}.
    • Always add @returns {void} to functions that returns nothing. Else, you never know whether there is no type because the function returns nothing, or you forgot to specify it.

  • Statics:

    • You must always add the @static tag by hand, even though you are commenting something in statics: else, the static member will appear as an instance member.
    • You must specify @property explicitly for statics, or they will appear as a global, instead of appearing as part of a class. Bug?

  • If you are using the config thing in ExtJs based code:

    • You must explicitly add the @cfg tag, or else the item will be signaled as a property, not a config.
    • To get getter/setter doc you must add the @accessor explicitly.
      Unfortunately, if a config entry is not marked with @cfg, there is no documentation generated for getters/setters.
    • If you have an item for which you don’t want a setter, create documentation for the setter and make it private.
      As explained below, you can create documentation for an item even if you don’t declare it explicitly.
    • Mark the config as required if needed: you do this by writing something like @cfg (required)

  • Always add the @readonly tag where appropriate. It is very important because this tells you not to juggle with something.

  • Always specify the @protected and @private tags. Because in javascript everything is public, your only way to know about this is the documentation.

  • Always specify the @abstract tag where appropriate. This will help implementors of derived clases quite a lot.

  • In a @returns tag, do not start comment in one line and continue in another, or it will not be considered part of the return comment. You can start the comment in the second line, placing the type in the first one.

  • To hide a class to documenation, make it @private.

  • In javascript you can always add a new property or function as you see fit, but, how do you document something you add in the middle of some function?
    You can document things that are not declared explicitly like this:

       /**
         * @cfg {String} name (required)
         * A very importand config option that must be specified.
         */
    

    I have to confess that it feels a bit strange to have comments for a property that is not there, and that I feel better if I provide the property itself, if at all possible. But I have to consider what’s worse, a ‘lonely’ comment or a comment with a property initialized to a fake and posibly wrong value.

Doubts

There are some things I’m not sure how to handle:

  • I’m not sure about how to specify an enumerated type (WARNING, INFO, …).
    Maybe the best way is to create a class just for that enumeration, and refer to that class as the type.
    I think this is good style, too, so everything should be ok with this approach.
  • I’m not sure about how to specify optional arguments: for example, in log4js-ext a logger’s logging methods can receive from 1 to n arguments, with very different purpose. I have not decided on a way to document that, beyond providing a link to examples.
  • I miss an additional visibility qualifier, to designate those methods visible only inside a subsystem (package, library, a group of classes…).
    Right now I just mark these as private, and add an extra ‘Package’ comment there (which is not visible in the API documentation, unfortunately).
  • I’m torn about when to make something private, and when not to document it.
    This being javascript, I feel that documenting a private method to the public is almost like telling them that they might use it if they are careful enough, and that to make it really private I should just avoid documenting it.

Huh?

And there are some things that make me wonder if I’m missing something…

  • The @template tag, does it really make sense to use it? Its meaning, to me, is equivalent to implying that a certain function is overridable, but, is not that the case for functions,
    by default?

Checking everything is ok

Once you’ve generated API doc, you’ll want to:

  • Take a look at warnings -though JSDuck generates a lot.
  • Look for the ‘unknown type’ string in JSDuck output!
  • Take a look at the API doc page with all your classes, and look for spurious globals.
  • In some cases, I’ve got warnings about missing images…that were there.
    When checking warnings, make sure that they are for real.
  • One of these days I will create doc that includes the ExtJs doc itself, so that the user can check whatever he wants *and* I do not get a thousand ‘class not found’ messages when generating the doc.

Minificación + compresión = 90% de ahorro en espacio

Log4js-ext cuenta con una docena de clases javascript, algunos “mods” a clases de ejemplo o de terceros, media docena de archivos de tests para JsTestDriver (también javascript), y varios archivos .css, con lo que la librería se va a casi dos docenas de archivos.

Hace unos días decidí publicar la versión 0.9 de log4js-ext. Una vez que uno hace una librería pública empieza a pensar en la conveniencia de pulirla.

Parece importante consolidar tanto los archivos javascript como los .css en un par de archivos, para facilitar su uso. Y minificar esos archivos para optimizar el rendimiento.

Consolidar y minificar javascript

La verdad, ExtJs, librería sobre la que funciona log4js-ext, proporciona una herramienta para consolidar/minificar javascript, pero la documentación es floja, y me ha parecido relativamente complicada.

Entre otras cosas, hay que generar un archivo de configuración que debe listar explícitamente todos los archivos javascript, nada de ‘añádeme todos los *.js en el directorio xyz’.

El problema no es que esto sea incómodo, ¡que lo es!, sino que fácilmente termina por generar defectos: añades un archivo de nada con una docena de constantes y se te olvida incluirlo en el archivo de configuración de la herramienta. Siendo javascript como es de permisivo, ni lo notas hasta varios días más tarde, cuando ya la has liado.

O sea, que al final he pasado de la herramienta de la gente de ExtJs/Sencha y he usado ant más YUI compressor.

Porque a) ant me es familiar, b) ya lo uso en ese proyecto para otras tareas (cuantas menos herramientas, mejor) y c) ant tiene unos mecanismo de inclusión/exclusión de archivos muy potentes, que me permiten incluir en la versión minificada de la librería justo los .js que quiero, excluyendo los de test, ejemplos, etc.

Aquí va la parte de la tarea que genera tanto un .js para desarrollo (para depurar) como un .js minificado (para producción): el primero lo monto a base de concatenar todos los .js, y el segundo usando el YUI Compressor sobre dicho archivo.

<target name="generate.packed-files" 
               description="Generate a single minified .js + a single .css">
   <property name="pack.outputdir" value="${basedir}/workdir/packed-files"/>
   <property name="yui.jar" 
       value="/devenv/lib/java/yuicompressor-2.4.7/yuicompressor-2.4.7.jar"/>

   <delete dir="${pack.outputdir}"/>
   <mkdir dir="${pack.outputdir}"/>

   <!-- Concatenate all .js files -->
   <concat destfile="${pack.outputdir}/${ant.project.name}-all-dev.js" 
         fixlastline="true">
     <fileset dir="${basedir}/WebContent/sm">
       <include name="**/*.js"/>
       <exclude name="**/*Test.js"/>
       <exclude name="**/test/*.js"/>
       <exclude name="**/examples/*.js"/>
     </fileset>

     <fileset dir="${basedir}/WebContent/smthirdparty" >
       <include name="**/*.js"/>
       <exclude name="**/*Test.js"/>
       <exclude name="**/test/*.js"/>
       <exclude name="**/examples/*.js"/>
     </fileset>     
   </concat>
   <echo message=
         "Generated unique dev time file ${pack.outputdir}/${ant.project.name}-all-dev.js"/>

   <!-- Compress resulting .js file -->
   <!-- Use YUI minifier -->
   <exec executable="cmd">
     <arg line="/c java -jar ${yui.jar} ${pack.outputdir}/${ant.project.name}-all-dev.js -o ${pack.outputdir}/${ant.project.name}-all.js"/>
   </exec>
   <echo message="Generated unique minified js file ${pack.outputdir}/${ant.project.name}-all-dev.js"/>
   
   ...

Nótese que concatenar a saco funciona porque el código .js que escribo permite concatenar archivos .js sin problemas: esto no es una limitación impuesta para poder generar un archivo único (eso sería mala cosa), sino que sale de forma natural al aplicar una serie de buenas prácticas que me parece importante aplicar en todo el código javascript en modo “no hola mundo”.

Ejecutando este script genero tanto la versión de desarrollo de la librería (log4js-ext-dev.js) como la versión de producción (log4js-ext.js): la primera me ocupa 96.208 bytes, y la segunda 31.427 bytes.

Por cierto, usando Tomcat como servidor y activando la compresión gzip, el tamaño del archivo final enviado al navegador pasa a ser de unos 9.300 bytes, es decir, un ahorro de espacio con respecto al tamaño original del código de ¡más del 90%!

Consolidar y minificar los .css en un único archivo

Otra cuestión a resolver es la de consolidar los diferentes .css en un único archivo, cómo no también minificado, para lo que uso juicer , una herramienta escrita en Ruby -llamándolo desde ant. Los resultados son también espectaculares.