AguasNegras

This was not supposed to be like this

19 enero, 2012
por Agustín Ventura
Sin comentarios

Amazon Elastic Beanstalk

Visto que con Heroku se me esta atragantando el tema del despliegue de JSF 2, me he decidido a seguir los cantos de sirena y probar Amazon Elastic Beanstalk. La publicidad dice que Amazon me dá gratuitamente un Tomcat 6 o Tomcat 7 en la nube, así que merecía darle una ojeada. Lo primero de lo que me doy cuenta es que Amazon Elastic Beanstalk en realidad no es solo un Tomcat, sino que mas bien podríamos definirlo como un agrupamiento de tecnologías que Amazon ya tenía que se ofrecen simplificadamente y bajo un mismo paragüas, como EC2, S3, EBS, CloudWatch, etc… La página de Beanstalk promete que se despliegan WARs normales y corrientes y que se puede usar cualquier librería Java con normalidad, a esto le sumamos Amazon SimpleDB como base de datos relacional y Amazon DynamoDB como NoSQL y tenemos un stack potentísimo a nuestra disposición. Esta la parte buena, ahora la mala.

La mala para empezar es que solo dan un año de uso gratuito. Pero no un año de tiempo de computación ni nada así, no. Un año de uso desde el momento del registro. Punto. Un poco rácano a mi parecer, preferiría que me limitasen más en recursos disponibles y no tener límite de tiempo, pero bueno.

Para continuar la muy mala. La muy mala es que, por lo que te piden en el registro, parece que vivo en Libia o en Cuba o qué sé yo. Durante el proceso de registro hay que crear una cuenta, proporcionar una tarjeta de crédito (¿no es gratuito? Entiendo que la pidan por si me paso de recursos, pero quizás en ese caso sería preferible echar abajo el servicio oportuno hasta el mes siguiente) y te hacen una llamada telefónica para que confirme con un pin que aparece en la pantalla tu identidad. Lo dicho, un poco paranoide.

Una vez habiendo pasado por todo este calvario, me decido a seguir el primer tutorial de ejemplo en Amazon.

Resumen

  1. Instalar Eclipse 3.7 JEE y el plugin AWS Toolkit para Eclipse.
  2. Crear un proyecto y configurar la cuenta de AWS.
  3. Análisis del proyecto.
  4. Crear un servidor en Elastic Beanstalk y desplegar el proyecto.
  5. Modificar el proyecto y redesplegar.
  6. Parar el servidor.

Paso 1

Descargar el Eclipse for Java EE Developers en la versión oportuna (en mi caso, Linux 64 bits) de aquí. Cuando haya bajado basta con descomprimirlo y ejecutarlo.

Para instalar el AWS Toolkit, una vez abierto el Eclipse, pulso Help > Install New Software … > Add y en el diálogo Add Repository, en Name pongo “AWS” y en Location “http://aws.amazon.com/eclipse”, Ok. Selecciono el nuevo repositorio en el desplegable y ya abajo sale “AWS Toolkit for Eclipse”. Lo selecciono y Next, Next, acepto las licencias, acepto que se instale software sin firmar (sigh) y reinicio Eclipse una vez instalado.

Entorno instalado, guay, sin mayor problema.

Paso 2

Para crear un proyecto para desplegar en AWS, hago click en File > New > Other > AWS > AWS Java Web Project.

Nuevo Proyecto AWS

Nuevo Proyecto AWS

Pulso Next. Para que un proyecto AWS sea desplegable necesita una información acerca de la cuenta del desarrollador, eso, junto con el nombre del proyecto es lo que tengo que configurar en esta pantalla. Como es la primera vez que entro, tengo que crear la cuenta, así que hago click en “Configure AWS Accounts” y veo la siguiente pantalla.

Configurar cuenta de AWS

Configurar cuenta de AWS

Y ahí estan los datos de la cuenta, hay que ponerle un nombre de cuenta (meramente identificativo para el Eclipse), una clave de acceso y una clave secreta. Como no tengo ni idea de que es eso, hago click encima de “find your existing AWS security credentials” y una vez logado en la página de AWS veo una pantalla que haciendo scroll tiene esta pinta:

Credenciales de Seguridad

Credenciales de Seguridad

Ahí (en el borrón) esta la clave de acceso y si hago click en “mostrar” veo la clave secreta. Pues nada, copiar y pegar a la ventana del Eclipse. Hago click en Ok y en la pantalla de configuración del proyecto dejo seleccionado “Basic Java Web Application”. Finish.

Paso 3

El proyecto recién creado es un proyecto web dinámico normal de Eclipse.

Proyecto Java Web AWS

Proyecto Java Web AWS

Tiene una carpeta src/ en la que se encuentran los fuentes de Java, de momento vacía, una carpeta webcontent en la que va el contenido web de la aplicación (jsp, html, css, js, png, gif, etc…) y dentro de ella, como es habitual, una carpeta WEB-INF con el web.xml dentro y un directorio lib (también vacío).

El web.xml es absolutamente normal, y lo único destacable es que dentro de src/ se encuentra un archivo llamado “AWSCredentials.properties” que contiene… las credenciales en texto plano. Bueno, yo no es por ser destroyer, al fín y al cabo esa información solo es accesible para el desarrollador (es decir, yo mismo), pero tampoco cuesta trabajo cifrarlo con algún hash, por aquello de mejorar algo la seguridad. Sus motivos tendrán.

De momento, el proyecto cumple lo prometido, Java Web normal y corriente. Ahora toca ver qué tal la ejecución.

Paso 4

Para ejecutar el proyecto, hago click encima de él con el botón derecho y selecciono Run As > Run on Server… En esta pantalla dejo seleccionado “Manually define a new server” y selecciono un AWS Elastic Beanstalk for Tomcat 6. En “Server host name” escribo Tomcat6AWS y pulso Next.

Nuevo Tomcat 6 AWS

Nuevo Tomcat 6 AWS

En la siguiente pantalla tengo que seleccionar para empezar una región en la que desplegar el proyecto. No sé si será un bug del plugin o que solo esta permitido ahí, pero solo me deja seleccionar US-East(Northern Virginia). Me hubiera gustado más seleccionar Europe(Ireland) por aquello del tiempo de latencia, pero bueno.

Lo siguiente son conceptos ya propios de Amazon Elastic Beanstalk. Una aplicación (application) es un producto software (un WAR, vaya) con una configuración y una versión determinada, mientras que un entorno (environment) es una instancia determinada de esa aplicación.

Pues vale, dejo marcado “Create a new application” y en Name pongo AWSJavaWeb. Para el Environment uso de nombre AWSJavaWeb igualmente.

Application y Environment

Application y Environment

En la siguiente pantalla, Advanced Configuration, la verdad que no entiendo nada, parece que es algún sistema de autenticación (¿otro?), pero sigo el tutorial y selecciono “Deploy with a key pair” y le doy a Add (la cruz verde). Me sale un diálogo para introducir un nombre y un directorio, de nombre uso AWSJavaWeb y el directorio lo dejo tal y como esta. Pulso Ok y pulso Finish.

Advanced Configuration

Advanced Configuration

Pero todavía sale un cuadro de diálogo más… pidiéndome la versión de la aplicación, claro. Escribo v20120118.01 (primera versión, 18 de Enero de 2012).

Versión de la Aplicación

Versión de la Aplicación

Pulso OK y espero mientras el cuadro de diálogo me va informando. Entiendo que el proceso es generar un WAR, subirlo a Amazon S3, crear una instancia de Amazon EC2 con el Tomcat 6 y desplegarlo… nada más… Cuando acaba:

Aplicación desplegada en Elastic Beanstalk

Aplicación desplegada en Elastic Beanstalk

Paso 5

Para ver como lleva esto los cambios, abro index.jsp y cambio el contenido de la etiqueta “title” a lo siguiente: Hello agustinventura AWS Java Web Application.

Guardo los cambios, click con el botón derecho en el proyecto, Run As > Run on Server… Selecciono el servidor que ya he creado y hago click en Finish. Me vuelve a salir el cuadro de dialogo para la versión, esta vez escribo v20120118.02 y OK.

Vuelvo a esperar (aunque menos) y…

Cambios Desplegados

Cambios Desplegados

Ahí esta, cambios desplegados en producción.

Paso 6

Para evitar que el servidor siga funcionando (y por tanto, me facturen), lo tengo qué parar. ¿Cómo? Pues en la pestaña Servers, lo selecciono y hago click en Stop (el botón rojo, o bien click con el botón derecho encima del servidor y Stop).

Y aquí me llevo la primera decepción, aunque tampoco es muy importante, tras 10 minutos esperando, decido entrar en la consola de aws ya que me parece extraño. Entro, selecciono AWS Elastic Bean Stalk, la aplicación y al hacer click en Events, veo que ya se ha parado… hace 10 minutos. Vaya, que el plugin se ha quedado colgado, habrá que reportarlo.

Consola AWS

Consola AWS

Conclusiones

Básicamente, y a falta de hacer algo más complejo, puedo dividir las conclusiones en pros y contras:

Pros:

  1. Parece que el desarrollo es Java Web “estándar”.
  2. El servidor de despliegue es un Tomcat (con todas sus cosas buenas y malas).
  3. El plugin para Eclipse, todo un detalle.

Contras:

  1. El proceso de alta… creo que fue más fácil darme de alta en Paypal…
  2. ¿Solo un año de prueba?¿De verdad?
  3. Los dos puntos anteriores me hacen concluir que el entorno es digamos… poco amistoso para desarrolladores aficionados, o porqué no, startups. En este sentido lleva ventaja Heroku.

El próximo paso que me gustaría dar es la prueba de fuego, a ver como se comporta para desplegar JSF 2 (que tengo atragantado en Heroku).

11 noviembre, 2011
por Agustín Ventura
Sin comentarios

Sun Java y OpenJDK en Linux

Para trabajar con Heroku, recomiendan usar OpenJDK, sin embargo, hasta el momento vengo usando el Sun JDK, así que voy a instalar el OpenJDK:

$ sudo apt-get install openjdk-6-jdk

Con esto se instala el OpenJDK 6, pero con un desafortunado efecto secundario:

$ java -version
java version "1.6.0_22"
OpenJDK Runtime Environment (IcedTea6 1.10.2) (6b22-1.10.2-0ubuntu1~11.04.1)
OpenJDK 64-Bit Server VM (build 20.0-b11, mixed mode)

Efectivamente, se ha establecido el OpenJDK como máquina virtual de Java por defecto, pero esto no es lo que quiero (al menos, yo no), me gustaría seguir usando el Sun JDK. Afortunadamente, qué JDK usar esta regido por el Linux Alternatives System, que básicamente es un sistema para poder cambiar entre varias implementaciones de un mismo programa, y Ubuntu trae un programa que se llama update-java-alternatives que te sirve para cambiar, en mi caso ha sido:

$ sudo update-java-alternatives -s java-6-sun

Listo, ahora ya:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

13 octubre, 2011
por Agustín Ventura
Sin comentarios

Despliegue de Spring Roo en Heroku

Hasta ahora, las aplicaciones que he desplegado eran muy básicas, hechas con jsp y acceso a base de datos a base de jdbc plano.

Estas tecnologías no estan mal (y de hecho son la base de todo lo posterior), pero lo más normal es utilizar frameworks para el desarrollo de aplicaciones Java.

La quinta práctica del libro de Java para Heroku es un despliegue de aplicaciones hechas con Spring Roo en Heroku. Spring Roo es un framework que viene a ser como Rails para Ruby. Simplifica el desarrollo con funciones tan básicas como crear el toString o el equals automáticamente o tan avanzadas como generar el CRUD de una base de datos directamente.

Así que con esta práctica voy a matar dos pájaros de un tiro, voy a probar Spring Roo y voy a desplegar un artefacto más complejo en Heroku. A ello.

Instalación de Spring Roo

Lo primero es descargar el framework de aquí. Después sigo las instrucciones de instalación:

  1. Descomprimir el archivo, en mi caso en $HOME/Java/spring-roo-1.1.5.RELEASE
  2. Crear enlace simbólico: sudo ln -s /home/case/Java/spring-roo-1.1.5.RELEASE/bin/roo.sh /usr/bin/roo
  3. Probar que funciona:
cd Java/spring-roo-1.1.5.RELEASE/
mkdir roo-test
roo quit

Si todo ha ido bien, debe haber salido Roo así en ASCII art monísimo. Ya esta instalado Spring Roo y puedo borrar roo-test, hala a retomar el tema.

Quinta Práctica

El objetivo de esta práctica es más bien sencillo, se va a usar Spring Roo para crear un ejemplo clásico, el Pet Clinic y se va a preparar esta aplicación creada con Roo para desplegar en Heroku.

Pasos:

  1. Crear la aplicación con Roo.
  2. Preparar el pom.xml añadiendo las dependencias adecuadas y la configuración correcta para su despliegue en Heroku.
  3. Crear una clase que arranque Jetty (el servidor integrado).
  4. Configurar la base de datos PostgreSQL en la aplicación.
  5. Probar la aplicación en local.
  6. Desplegar en Heroku.

Paso 1

Crear la aplicación es más bien sencillo:

cd Heroku
mkdir petclinic
cd petclinic
roo script --file clinic.roo

Me escupe un montón de logs por la consola y en teoría, listo. Examinando un poco la aplicación, pues nada, es una aplicación Java que sigue la estructura de Maven, con su pom.xml y nada, todo perfecto, sus entidades, sus controladores… su páginas web, tiene pinta de estar hecho con Spring MVC… vale, al lío.

Paso 2

En este paso, lo primero es añadir Jetty y PostgreSQL al pom.xml:

<dependency>
		<groupId>org.eclipse.jetty</groupId>
		<artifactId>jetty-webapp</artifactId>
		<version>7.4.4.v20110707</version>
	</dependency>
	<dependency>
		<groupId>org.mortbay.jetty</groupId>
		<artifactId>jsp-2.1-glassfish</artifactId>
		<version>2.1.v20100127</version>
	</dependency>
	<dependency>
		<groupId>postgresql</groupId>
		<artifactId>postgresql</artifactId>
		<version>9.0-801.jdbc4</version>
	</dependency>

Ahora cambio el scope de servlet-api de provided a compile. Si se usa Tomcat, Tomcat incluye la librería servlet.jar (la API de Servlet), pero Jetty no, así que hay que descargarla. Hay que cambiar igual el scope de org.springframework.roo.annotations, si la ejecuto en local con Roo, estará la librería de anotaciones de Roo, pero claro, en Heroku no esta.

Siguiente paso, añadir el plugin appassembler para que genere el script con el que arrancar la aplicación:

<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>appassembler-maven-plugin</artifactId>
			<version>1.1.1</version>
			<executions>
				<execution>
					<phase>package</phase>
					<goals><goal>assemble</goal></goals>
					<configuration>
						<assembleDirectory>target</assembleDirectory>
						<extraJvmArguments>-Xmx512m</extraJvmArguments>
						<programs>
							<program>
								<mainClass>com.springsource.petclinic.PetclinicMain</mainClass>
								<name>webapp</name>
							</program>
						</programs>
					</configuration>
				</execution>
			</executions>
		</plugin>

Y por último, hay que quitar la línea de packaging para que genere un jar en vez de un war, listo.

Paso 3

Ahora hay que crear la clase Main.java en src/main/java, exactamente igual que la que puse en la primera práctica, tampoco tiene más historia.

Paso 4

Para configurar la aplicación para que use PostgreSQL hay que tocar el applicationContext.xml, que es el archivo de configuración de Spring. Se encuentra en src/main/resources/META-INF/spring. Aquí, hay que cambiar las propiedades username, password y url por tan solo url de la siguiente manera:

Que es el parseo para la cadena de conexión a PostgreSQL que ya ví. En el applicationContext.xml veo que el driver de conexión a la base de datos está parametrizado, así que tengo que cambiarlo, en el mismo directorio esta un archivo database.properties en el que esta la propiedad que me interesa, database.driverClassName, lo dejo así:

database.driverClassName=org.postgresql.Driver

Por último, en el persistence.xml (que esta en src/main/resources/META-INF), hay que cambiar el valor del hibernate.dialect, a org.hibernate.dialect.PostgreSQLDialect.

Paso 5

Todo listo, ahora toca probar en local:

export REPO=~/.m2/repository/
export DATABASE_URL=postgres://helloheroku:helloheroku@localhost/helloheroku
mvn install
sh target/bin/webapp

Listo, entro en http://localhost:8080 y ahí esta el PetClinic con Spring Roo y contra PostgreSQL. Genial

Paso 6

Para el despliegue de la aplicación en Heroku, primero hay que crear el Procfile con la definición de la actividad web:

gedit Procfile

Y añado:

web:sh target/bin/webapp

Lo añado todo a un repositorio de git, creo el entorno de Heroku, lo subo todo a Heroku y GitHub y abro la aplicación:

git init
 
git add pom.xml Procfile src/
 
heroku create --stack cedar
 
git push github master
 
git push heroku master
 
heroku open

Y listo, funcionando!

Código en GitHub

GitHub

Conclusiones

Este ejemplo es, de lejos, el más interesante, ya que demuestra tres cosas:

  1. Que Heroku parece ser capaz de trabajar sin problemas con los frameworks mayoritarios de Java.
  2. Que realmente, los frameworks de Java son tremendamente potentes si se sabe como usarlos, aportando una gran flexibilidad.
  3. Y por último, creo que mi primera impresión estaba justificada. Tenemos un entorno capaz de acercar el mundo Java EE a la mayoría de desarrolladores… con todo lo que ello implica. Ahora es cosa nuestra aprovecharlo o no.

Y con esto, he terminado con Heroku de momento, me sigue quedando pendiente clarificar el tema de dynos, bds, etc… A ver si me pongo…

22 septiembre, 2011
por Agustín Ventura
Sin comentarios

Add-ons para Heroku

La arquitectura de Heroku es modular, es decir, se pueden añadir funcionalidades al Stack mediante piezas de software llamadas add-ons. Un ejemplo de eso lo vi aquí cuando añadí el soporte para gestión de releases a través de un add-on.

Hay bastantes más add-ons disponibles en la página, para enviar sms, para conectar como amazon rds, etc… Los hay gratuitos, de pago, en beta… en fín, de todo tipo.

En esta práctica voy a añadir el add-on de Redis.

Instalación de Redis

Pues por fín una instalación de un solo paso:

sudo apt-get install redis-server

Una vez instalado, para verificar:

redis-cli

Y debe responder una bonita consola de la que salgo con Ctrl-C.

Cuarta Práctica

Dado que Redis es un almacén del tipo clave – valor (key-value), se da bastante bien para hacer cachés, lo que haré será configurar Redis como un almacenamiento que expire cada 30 segundos, es decir que solo se leerá de la base de datos realmente cada 30 segundos, el resto de las veces se impactará contra Redis. Esto es muy burdo, claro, pero sirve para hacer una demo.

Pasos:

  1. Configurar la aplicación para usar Redis.
  2. Actualizar TickDAO para que use Redis.
  3. Probar la aplicación en local.
  4. Desplegar en Heroku.

Paso 1

Esta parte es tan sencilla como añadir al pom.xml esto:

<dependency>
         <groupId>redis.clients</groupId> 
         <artifactId>jedis</artifactId>
         <version>2.0.0</version>
</dependency>

Paso 2
En este paso se modifica TickDAO para primero intente leer los ticks de Redis y si no vaya a la base de datos.
En primer lugar añado como variables estáticas un pool de conexiones a Redis (jedisPool) y la clave de los ticks:

private static JedisPool jedisPool;
private static final String TICKCOUNT_KEY = "tickcount";

A continuación, en el bloque de código static en el que inicializo PostgreSQL, aprovecho y cargo también el pool de Redis, solo resalto que la URL de Redis se lee de una variable de entorno llamada REDISTOGO_URL:

//Inicialización de Redis
Pattern REDIS_URL_PATTERN = Pattern.compile("^redis://([^:]*):([^@]*)@([^:]*):([^/]*)(/)?");
Matcher matcher = REDIS_URL_PATTERN.matcher(System.getenv("REDISTOGO_URL"));
matcher.matches();
Config config = new Config();
config.testOnBorrow = true;
jedisPool = new JedisPool(config, matcher.group(3),
Integer.parseInt(matcher.group(4)), Protocol.DEFAULT_TIMEOUT, matcher.group(2));

Por último, el getTickCount() queda así:

public int getTickCount() throws SQLException {
		Jedis jedis = jedisPool.getResource();
		int tickcount = 0;
		String tickcountValue = jedis.get(TICKCOUNT_KEY);
		if (tickcountValue != null) {
			System.out.println("read from redis cache");
			tickcount = Integer.parseInt(tickcountValue);
		} else {
			tickcount = getTickcountFromDb();
			jedis.setex(TICKCOUNT_KEY, 30, String.valueOf(tickcount));
		}
		jedisPool.returnResource(jedis);
		return tickcount;
	}

Es decir, cuando se va a hacer una lectura, primero se hace desde Redis, si el valor no existe (es null) se lee de base de datos y se inserta en Redis.
A probar.

Paso 3
Aquí vendría todo el mvn install, etc, pero antes, como ya estoy harto de andar exportando las variables de entorno cada vez que voy a hacer algo en local, voy a hacer dos cosas.
Primero, voy a crear un exportarVariables.sh que exportar tanto REPO, como POSTGRESQL_URL como REDISTOGO_URL.
Así que:

gedit exportarVariables.sh

Y pongo lo siguiente:

export REPO=~/.m2/repository
export DATABASE_URL=postgres://helloheroku:helloheroku@localhost/helloheroku
export REDISTOGO_URL=redis://:@localhost:6379/

Ahora hago el script ejecutable:

chmod 777 exportarVariables.sh

Y como segundo paso, ya que este archivo solo es aplicable en local, lo añado al .gitignore. Hala, listo, ahora sí que sigo de la manera habitual:

mvn install
. ./exportarVariables.sh
sh target/bin/webapp

Y ahí esta, si entro en localhost:8080/ticks.jsp, veré que siempre muestra los mismos ticks… hasta que pasan 30 segundos, que actualiza la cuenta con el número de veces que haya recargado la página. Es decir, se esta escribiendo en base de datos, pero no se esta leyendo. Hasta pasados 30 segundos, claro.

Paso 4
Vale, pues para desplegar, primero tengo que añadir el add-on de Redis al Stack de esta aplicación en Heroku:

heroku addons:add redistogo:nano
-----> Adding redistogo:nano to fierce-autumn-4530... done, v11 (free)

Correcto, ahora basta con dar los pasos habituales:

git add .
git commit -m "añadido Redis para caché de lectura"
git push github master
git push heroku  master

Listo, con esto ya hago un heroku open y abro ticks.jsp, puedo ver que el comportamiento es exactamente igual que cuando ejecuto en local.

Código en GitHub
GitHub

Conclusiones
Añadir add-ons a Heroku es trivial, en general consiste en bajar la librería que proporciona la api y listo (teniendo instalado el servicio en local, claro).
También es interesante que si escalo el proceso tick a un par de dynos, siguen impactando contra Redis, es decir, no levantan su propia instancia de Redis, realmente esta funcionando como una caché en RAM.

22 septiembre, 2011
por Agustín Ventura
Sin comentarios

Procesos Java en Heroku

Heroku no solo sirve para ejecutar aplicaciones web Java, sino que en realidad sirve para ejecutar cualquier tipo de aplicación Java. Observando el proyecto podemos ver que hay dos aplicaciones bien diferenciadas, la aplicación web (que se lanza a través del script webapp generado por Maven) y la aplicación SchemaGenerator, ejecutada a través del script schemaGenerator. Mientras que la primera es una aplicación web normal y corriente, SchemaGenerator es simplemente un proceso Java puro, se lanza se ejecuta y termina sin ningún tipo de interfaz gráfica involucrada.

A este tipo de proceso en Heroku le llaman “worker process”, podría traducirlo de cualquier manera, pero paso, así que se queda como proceso worker, hala.

Tercer Práctica

El objetivo de esta práctica es desarrollar una aplicación que inserte un tick en la base de datos cada segundo. Se podrá visualizar refrescando la misma página ticks.jsp y posteriormente la escalaré a dos dynos.

Pasos:

  1. Crear la clase Ticker.java que insertará un tick en la base de datos cada segundo.
  2. Probar la aplicación en local.
  3. Desplegar la aplicación en Heroku, escalarla a dos dynos y detenerla.

Paso 1

Creo la clase Ticker.java, el código es trivial, con un while(true) y un sleep(1000), de todas formas queda en GitHub.

Se declara el programa en el pom.xml para generar el script que lo lanza:

	es.aguasnegras.helloheroku.Ticker
	ticker

Paso 2

Se instala en local y se ejecuta:

mvn install
export REPO=~/.m2/repository
export DATABASE_URL=postgres://helloheroku:helloheroku@localhost/helloheroku
sh target/bin/ticker

Y en otra consola:

export REPO=~/.m2/repository
export DATABASE_URL=postgres://helloheroku:helloheroku@localhost/helloheroku
sh target/bin/ticker

Ahora puedo comprobar en localhost:8080/ticks.jsp que efectivamente, se van actualizando los ticks independientemente de la aplicación.

Paso 3
Vale, ahora a desplegarla en Heroku. Para poder ejecutar el proceso en Heroku en su propio dyno, hay que declararla en el Procfile:

tick: sh target/bin/ticker

Listo, añado a git y subo a GitHub y Heroku:

git add .
git commit -m "añadido proceso worker"
git push github master
git push heroku master

Si abro http://fierce-autumn-4530.herokuapp.com/ticks.jsp puedo ver que todo sigue igual, cada vez que recargo la página se añade un tick a la base de datos. Voy a arrancar el worker:

heroku scale tick=2

Si hago un heroku ps me confirma que se han arrancado dos procesos, y es más, un heroku logs -t va refrescando la salida con dos ticks cada segundo, uno tick.1 y otro tick.2.
Además el jsp me confirma que se han ido insertando en la base de datos.
Bueno, pues ya esta. A poner el código en GitHub

Código en GitHub
GitHub

Conclusiones
Bueno, pues ya he visto como se ejecuta un proceso demonio y he trasteado un poco el Procfile, que es otro fleco que convendría investigar un poco, a ver que más cosas se pueden hacer.
Es curioso que el proceso se lanza mediante un scale y no un heroku run, supongo que podría igualmente con el heroku run… a ver. Efectivamente, se puede ejecutar igualmente con un heroku run, la diferencia es que no se puede escalar, claro.

18 septiembre, 2011
por Agustín Ventura
Sin comentarios

Java, PostgreSQL y Heroku

Ya he visto como desplegar en Heroku, pero lo que he desplegado no llega ni al nivel de aplicación web, en realidad es una página web estática y punto. Para poder considerarla aplicación web ha de tener algún tipo de contenido dinámico, normalmente este contenido se genera de algún almacén persistente, usualmente una base de datos.
Por tanto voy a ver como me las apaño para poner una base de datos en Heroku y atacarla desde mi aplicación. En realidad en la primera práctica, el comentado “heroku config” me daba alguna pista, supongo que usaré una base de datos PostgreSQL alojada en AmazonWeb Services, vamos a ver si es así, pero antes…

Instalación de PostgreSQL

Para poder desarrollar y desplegar en local hará falta una instancia de PostgreSQL. Afortunadamente la instalación en debian/ubuntu/mint es tan sencilla como:

sudo apt-get install postgresql

PostgreSQL es un metapaquete que contiene varias utilidades, el servidor (versión 8.4), el cliente (versión 8.4) y sus dependencias.

Para no liarme voy a seguir el Heroku for Java Workbook a la hora de configurar el PostgreSQL.

Lo primero es crear un usuario de PostgreSQL con privilegios de superusuario. En el libro lo llaman foo, pero como a mí no me gusta un nombre tan genérico lo voy a llamar helloheroku… mucho mejor, no veas… :P

sudo -u postgres createuser -P helloheroku

Como viene siendo habitual, ahora hay que contestar una serie de preguntas:

  1. Contraseña: helloheroku
  2. Repetir la contraseña: pues eso
  3. ¿Es superusuario?: s

Creo la base de datos como tal. Será una base de datos en localhost, se llamará helloheroku y la creará el usuario helloheroku:

createdb -U helloheroku -W -h localhost helloheroku

De contraseña, helloheroku. Y pruebo la conexión:

psql -U helloheroku -W -h localhost helloheroku
Contraseña para usuario helloheroku:
psql (8.4.8)
conexión SSL (cifrado: DHE-RSA-AES256-SHA, bits: 256)
Digite «help» para obtener ayuda.
 
helloheroku=# \q

De propina, como trabajar con una base de datos en modo CLI es un poco… árido, voy a instalar pgAdmin:

sudo apt-get install pgadmin3

Y listo, ahora sí, práctica dos.

Segunda Práctica

El objetivo de esta práctica es crear una página web que guarde el número de veces que es cargada. Así de fácil.

Para ello se usará una tabla de base de datos que guardará un timestamp. Se creará también una página en JSP que cada vez que se cargue, almacenará un nuevo timestamp (llamado tick), en la base de datos y mostrará el número de ticks que hay en total.

Esta solución se puede hacer tan fácil o compleja como se quiera. Pero en un alarde de sentido común, el cuaderno de trabajo propone hacerlo en JSP y JDBC plano, sin más historias. Es muy interesante el comentario de que Heroku es compatible con Hibernate o JPA.

En resumen, estos son los pasos que se darán:

  1. Configurar la aplicación web del tutorial anterior para usar PostgreSQL.
  2. Crear el DAO en Java (la clase que se encargará de acceder a la base de datos).
  3. Crear la página JSP que al cargarse inserte un tick en la base de datos y muestre los que hay.
  4. Crear una clase que se ejecutará al ser desplegada la aplicación y que creará el esquema de base de datos.
  5. Probar en local.
  6. Desplegar en Heroku.

Hay que reconocer que el paso cuatro es un poco raro, lo más normal sería ejecutar previamente las operaciones que sean sobre la base de datos por separado, pero vamos a ello.

Paso 1

Para añadir el soporte de PostgreSQL basta con añadir la dependencia al pom.xml:

<!-- PostgreSQL -->
<dependency>
	<groupId>postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<version>9.0-801.jdbc4</version>
</dependency>

Y listo, ahora se puede crear el DAO.

Paso 2

No voy a copiar la clase DAO ya que es bastante convencional, basta con copiarla y pegarla en un archivo llamado TickDAO.java en el mismo directorio que el Main.java (no debiera ser así pero tampoco me voy a poner exquisito para una prueba de concepto). Sin embargo si que voy a comentar las cosas que me vayan llamando la atención.

Lo primero es que no se crea ningún tipo de pool de conexiones ni nada, lo único que hay es un bloque de código estático que lee la url de la base de datos de la variable de entorno del dyno “DATABASE_URL” y la parsea para adaptarla a lo que espera jdbc, guardándola después en una variable estática.

La conexión se abre y se cierra en cada uno de los tres métodos que acceden a la base de datos, que son para insertar un tick nuevo, para leer cuantos ticks hay y para crear la base de datos (supongo que este método se llamará desde la clase que se ejecuta para crear el esquema de la base de datos).

También es interesante ver que no se hace ningún tipo de tratamiento de errores, aparte de un finally para cerrar la conexión. Las excepciones simplemente se relanzan para arriba.

Asímismo no hay ningún framework de gestión de logs, se usa System.out. Ya en la práctica anterior, en la sección de logs decía que el comando “heroku logs” funcionaba sobre System.out y System.err, aún así molaría tener algún tipo de framework para poder gestionar más fácilmente los logs y activar y desactivar declarativamente los de debug, por ejemplo. Supongo que no será muy complicado de integrar (en realidad espero que sea inmediato).

Nada más que comentar, la clase no es ninguna maravilla, pero funcionará y es para lo que es.

Paso 3

El jsp lo pongo directamente en src/main/webapp, y se llama ticks.jsp. No tiene nada de particular, son dos scriptlets JSP.

El primero instancia un TickDAO y llama a la función insertTick.

El segundo llama a la función getTickCount y listo.

Me hace gracia que comentan que es importante saber que el acceso a base de datos no esta transaccionado y por tanto dos usuarios concurrentes podrían tener problemas de lecturas no consistentes. En fín, nunca es tarde para inculcar buenas prácticas, supongo.

Paso 4

En este paso en general se añadirán los mecanismos para hacer que la base de datos exista al ejecutar la aplicación.

En primer lugar, creo una clase SchemaCreator.java en src/es/aguasnegras/helloheroku que simplemente instancia un TickDAO y llama a su función createTable().

Después añado al pom.xml lo siguiente en la sección programs del plugin appassembler-maven-plugin como primer elemento:

<program>
	<mainClass>es.aguasnegras.helloheroku.SchemaCreator</mainClass>
	<name>schemaCreator</name>
</program>

De esta manera consigo que cuando se ejecuta Maven sobre el proyecto, se generan scripts (tanto de shell como .bat) que me permiten lanzar tanto la clase SchemaCreator como Main.

Me surge una duda… efectivamente, el createTable() hace un “drop table if exists”, así que cada vez que se ejecute el script, se creará la base de datos desde cero. Tampoco es que me importe, vaya.

Bueno, pues ya esta, ahora habrá que probarlo en local, digo yo.

Paso 5

Para probar la aplicación, hay que exportar la variable de entorno DATABASE_URL:

export DATABASE_URL=postgres://helloheroku:helloheroku@localhost/helloheroku

No hay que olvidar exportar también la variable REPO:

export REPO=~/.m2/repository

Ahora hay que ejecutar el script que crea la base de datos:

sh target/bin/schemaCreator

Y por último, se lanza la aplicación:

sh target/bin/webapp

Si todo ha ido bien, al abrir en el navegador, http://localhost:8080/ticks.jsp, debería ver 1 Ticks… efectivamente, conforme le voy dando a recargar me va aumentando secuencialmente. Hala, pos yasta, ahora a subir a Heroku.

Paso 6

Los pasos, son los de siempre, añadir a git y enviar a heroku (en mi caso, también a GitHub):

git add .
git commit -m "Añadida interacción con base de datos PostgreSQL como ejemplo"
git push github master
git push heroku master

Esta última línea lanza Maven sobre el proyecto como ya se vió y lo compila correctamente.

Para lanzar el proceso schemaCreator, heroku me permite lanzar comandos remotos:

heroku run "sh target/bin/schemaCreator"
Running sh target/bin/schemaCreator attached to terminal... up, run.1
the jdbc connection string is: jdbc:postgresql://ec2-107-20-227-173.compute-1.amazonaws.com/...?user=...&amp;password=...
Creating ticks table.

Abro http://fierce-autumn-4530.herokuapp.com/ticks.jsp en el navegador… et voilà, andando.

Código en GitHub

GitHubConclusiones

Heroku soporta una base de datos plenamente madura, como es PostgreSQL y con un esfuerzo mínimo, de hecho, hay que invertir más tiempo en configurarla en local que en remoto.

De nuevo las dudas que me surgen son administrativas, ¿cuánto espacio tengo de tablespace? ¿Y de conexiones concurrentes? etc…

Quedaría también pendiente usar alguna tecnología más sofísticada para conectar a base de datos, usando algún tipo de pooling de conexiones, pero no me parece complicado para nada.

16 septiembre, 2011
por Agustín Ventura
3 Comentarios

Java en Heroku

Una de las cosas que más me impresionó de Ruby on Rails, más que el framework en sí o el lenguaje, fue el excelente soporte que había creado la comunidad. Hablo específicamente de como se enlazaba tu proyecto local con GitHub y GitHub con Heroku, con lo cual podías tener el proyecto en producción en cuestión de minutos.

Esto es algo que lamentablemente no he visto en Java en los cinco años que llevo dedicado a estos menesteres, y menos con un alojamiento de la categoría de Heroku (otro día hablaré sobre ello y el PaaS, etc…). Pues bien, el pasado 25 de Agosto, Heroku añadió soporte para Java. Este paso me parece crucial. Desde mi punto de vista, la gran fortaleza de PHP, RoR, etc… viene de la comunidad, es un lenguaje “accesible” a cualquiera, basta con comparar los hostings existentes para PHP y los existentes para Java. Hay otras plataformas para Java, por ejemplo, Google App Engine, pero desde mi punto de vista son mucho más restrictivas que Heroku. Como de todas formas esto es solo una impresión, he decidido seguir el Heroku For Java Workbook a ver qué tal resulta :)

Requisitos

  1. Cuenta en GitHub.
  2. Cuenta en Heroku.
  3. Tener instalado Git

Instalación de las herramientas de línea de comando de Heroku (heroku)

Lo primero es instalar las herramientas de linea de comando de Heroku, llamadas heroku (sin mayúscula), estas a su vez tienen otros requisitos:

  1. Instalar Ruby (apt-get install ruby).
  2. Instalar RubyGems (se descarga de RubyGems.org y se ejecuta sudo ruby setup.rb)
  3. Se instala el cliente en sí: sudo gem install heroku.
  4. Para verificar si todo ha ido bien, el comando heroku version debe devolver algo como heroku-gem/2.6.1

El siguiente paso es añadir nuestra clave SSH pública a Heroku, primero se hace login: heroku auth:login, e introducimos nuestro email y contraseña.

A continuación basta con hacer heroku keys:add y listo, añadida nuestra clave SSH.

Instalación de Maven

La instalación de Maven viene descrita en su misma página de descarga de manera bastante sucinta. En cualquier caso estos son los pasos a dar:

  1. Descomprimir el archivo (en mi caso en $HOME/Herramientas).
  2. Añadir la variable de entorno M2_HOME, que es el directorio de Maven (en este caso sería export M2_HOME=$HOME/Herramientas/apache-maven-3.0.3)
  3. Añadir la variable de entorno M2 que son los binarios de Maven (export M2=$M2_HOME/bin)
  4. Añadir la variable MAVEN_OPTS al entorno. Esta variable son comandos que se pasan a la JVM que ejecuta Maven, en nuestro caso será para aumentar la memoria disponible para la ejecución de Maven (por defecto son 64 Mb, pero la vamos a aumentar a 256): export MAVEN_OPTS=”-Xms256m -Xmx512m”.
  5. Añadir la variable M2 al Path: export PATH=$M2:$PATH
  6. Si ejecutamos mvn –version debería salir la versión actual de Maven (3.0.3), la version de la JVM, etc…

Primera Práctica

El objetivo de esta primera práctica es construir una aplicación web, es decir un simple html con su web-inf, etc. Los pasos son cuatro:

  1. Crear la aplicación web a partir de un arquetipo de Maven.
  2. Probar la aplicación web en local.
  3. Desplegar la aplicación web en Heroku.
  4. Escalar la aplicación web en Heroku.
  5. Ver los logs de la aplicación en Heroku.
  6. Deshacer una publicación en Heroku.

Paso 1

Entro en mi directorio de trabajo (Heroku) y una vez dentro genero el proyecto con la siguiente línea:

mvn archetype:generate -DarchetypeCatalog=http://maven.publicstaticvoidmain.net/archetype-catalog.xml

Ahora Maven irá preguntando por una configuración básica del proyecto, estas son las respuestas que he ido dando:

  1. Pregunta el arquetipo que se desea usar, realmente solo hay uno, así que pulso 1.
  2. GroupId es el identificador del equipo de desarrollo, en este caso y por nomenclatura Java (es también el prefijo de toda la paquetería): es.aguasnegras
  3. ArtifactId es el nombre del proyecto: helloheroku
  4. Version se deja en blanco, será la 1.0-SNAPSHOT
  5. Package es la paquetería del proyecto, introduzco es.aguasnegras.helloheroku
  6. Por último, muestra un resumen y pide confirmación: Y.

Con esto tengo el proyecto creado según la estructura de directorio estándar de Maven. Debo tener un index.html, un Main.java y un pom.xml que contiene la configuración del proyecto según Maven.

Lo interesante es el Main.java. En Heroku no despliego sobre un servidor de aplicaciones (caso usual en Java) sino que es la misma aplicación la que crea su servidor, Jetty (por eso el arquetipo de Maven era embedded-jetty-archetype). Esto tiene sus pros y sus contras, pero de momento esta bien. Se puede ver en el main de Main.java como se instancia el servidor.

Una ventaja de esto es que voy a poder probar la aplicación en local sin tener que caer en todo el tedio de instalar un Tomcat, un Weblogic, un JBoss o un Glassfish. Y ese es el siguiente paso.

Paso 2

Primero hay que tener en cuenta que Maven me gestiona las dependencias (librerías, vaya) en tiempo de compilación, si quiero tenerlas disponibles en tiempo de ejecución (cuando arranque en local), tengo que exportar una variable llamada repo:

export REPO=~/.m2/repository

Para arrancar la aplicación en local, entro en el directorio (helloheroku) y ejecuto:

mvn install
sh target/bin/webapp

Abro en un navegador localhost:8080 y ahí esta: “hello, world”.

Para parar la aplicación, Ctrl-c en la consola y listo. Una vez probada en local, hay que moverla (pasarla, exportarla, publicarla… publicarla, sí, me gusta) a Heroku.

Paso 3

Para publicar la aplicación en Heroku, primero hay que crear un archivo llamado “Procfile” en el raíz del proyecto y añadirle una línea describiendo como se arranca la aplicación:

touch Procfile
echo web: sh target/bin/webapp | tee Procfile

Creo un repositorio de git para el proyecto:

git init
git add .
git commit -m "commit inicial del proyecto helloheroku"

Ahora viene lo bueno, la parte de cacharreo con Heroku.

Primero creo el stack de Heroku que va a alojar la aplicación, este stack tiene que ser del tipo “cedar” que es el único que admite aplicaciones Java:

heroku create --stack cedar
Creating fierce-autumn-4530... done, stack is cedar
http://fierce-autumn-4530.herokuapp.com/ | git@heroku.com:fierce-autumn-4530.git
Git remote heroku added

El comando me ha devuelto una URL http y otra para git. Si abro la URL en un navegador, me dice que enhorabuena, tengo creada mi app.
Ahora, con git, envío el master local a Heroku:

git push heroku master

Cuando llegue a Heroku veré en la consola que se lanza un proceso de Maven (mvn install) y que se detecta el Procfile y se lanza la aplicación web. Por último, me informa de que se ha desplegado en la URL correctamente.

-----> Heroku receiving push
-----> Java app detected
-----> Installing Maven 3.0.3..... done
-----> Installing settings.xml..... done
-----> executing .maven/bin/mvn -B -Duser.home=/tmp/build_s6y8o29xnjfx -s .m2/settings.xml -DskipTests=true clean install
 
-----> Discovering process types
       Procfile declares types -&gt; web
-----> Compiled slug size is 12.8MB
-----> Launching... done, v5
       http://fierce-autumn-4530.herokuapp.com deployed to Heroku

Si ahora abro http://fierce-autumn-4530.herokuapp.com/ en el navegador, puedo ver el “hello, world”.

Bueno, pues ya esta desplegada la aplicación en un servidor, ahora, ¿cómo se aumenta?

Paso 4

Ya esta desplegada la aplicación en un servidor, pero claro, esto no sería la nube si no pudiera ampliar fácilmente los servidores en los que ejecuta. Voy a aumentar la aplicación a dos servidores:

heroku scale web=2
Scaling web processes...  This action will cause your account to be billed at the end of the month
 For more information, see http://docs.heroku.com/billing
 Are you sure you want to do this? (y/n) y
Scaling web processes... done, now running 2

La verdad que no entiendo muy bien como pretenden cobrarme, porque no he dado ningún dato… pero oye… ellos mismos. Aunque habrá que mirar más a fondo el tema de como cobran.

Por último, voy a verificar que efectivamente estan corriendo dos instancias y voy a reducir a una de nuevo:

heroku ps
Process       State               Command
------------  ------------------  ------------------------------
web.1         up for 17m          sh target/bin/webapp
web.2         up for 2m           sh target/bin/webapp
 
heroku scale web=1
Scaling web processes... done, now running 1

Hasta el momento toda esta información de salida es bastante… escueta. ¿Cómo se ven los logs?

Paso 5

Pues muy fácil, hay dos forma de ver los logs, estática o dinámica. Estática, me muestra lo que hay hasta el momento:

heroku logs

Dinámica, no deja de ser un tail, va actualizando el log según se van creando mensajes nuevos:

heroku logs -t

Paso 6

Por último, Heroku ofrece soporte para control de versiones. No me refiero solo a código, si no también, como en el ejemplo a variables de entorno, por ejemplo.

Primero me aseguro de que el servidor tiene soporte para el control de releases, mediante el addon de releases:

heroku addons:add releases:basic
-----> Adding releases:basic to fierce-autumn-4530... failed !    
releases:basic add-on already added.

Esta añadido, ahora voy a añadir una variable de entorno y a verificar que se ha hecho:

heroku config:add MYVAR=42
Adding config vars:  MYVAR =&gt; 42
Restarting app... done, v6.
 
heroku config
DATABASE_URL        => postgres://tpqbfpkzqb:OILC1r62mtQ5YkqKTeA7@ec2-107-20-227-173.compute-1.amazonaws.com/tpqbfpkzqb
JAVA_OPTS           => -Xmx384m -Xss512k -XX:+UseCompressedOops
MAVEN_OPTS          => -Xmx384m -Xss512k -XX:+UseCompressedOops
MYVAR               => 42
PATH                => .maven/bin:/usr/local/bin:/usr/bin:/bin
REPO                => /app/.m2/repository
SHARED_DATABASE_URL => postgres://tpqbfpkzqb:OILC1r62mtQ5YkqKTeA7@ec2-107-20-227-173.compute-1.amazonaws.com/tpqbfpkzqb

Muy bien, pues ahora, toca volver atrás:

heroku releases
Rel   Change                          By                    When
----    ----------------------          ----------                   ----------
v6    Config add MYVAR    agustinventura@gma..  45 seconds ago           
v5    Deploy cd3c58f          agustinventura@gma..  29 minutes ago           
 
heroku rollback
Rolled back to v5
 
heroku config
DATABASE_URL        => postgres://tpqbfpkzqb:OILC1r62mtQ5YkqKTeA7@ec2-107-20-227-173.compute-1.amazonaws.com/tpqbfpkzqb
JAVA_OPTS           => -Xmx384m -Xss512k -XX:+UseCompressedOops
MAVEN_OPTS          => -Xmx384m -Xss512k -XX:+UseCompressedOops
PATH                => .maven/bin:/usr/local/bin:/usr/bin:/bin
REPO                => /app/.m2/repository
SHARED_DATABASE_URL => postgres://tpqbfpkzqb:OILC1r62mtQ5YkqKTeA7@ec2-107-20-227-173.compute-1.amazonaws.com/tpqbfpkzqb

¡Listo! Hecha la vuelta atrás a la última publicación estable.

Código en GitHub

GitHub

Conclusiones

Pues de momento parece que Heroku ofrece lo que promete, uso de Java estándar, e integración con herramientas actuales: git y Maven. Además me ha sorprendido gratamente la herramienta de consola con más utilidades de lo que parece y que hace muy sencillo aumentar el número de instancias de la aplicación.

Esto sí, echo en falta… un plugin de Eclipse, tendré que investigar si existe o esta en desarrollo.

Igualmente tengo que investigar bien las opciones de facturación y algunos conceptos como dyno, stack, etc…

30 agosto, 2011
por Agustín Ventura
Sin comentarios

GitHub y Markdown

Si entro en el repositorio PruebaGit que cree en este artículo, GitHub es tan amable de avisarme de que no encuentra un archivo README.

En primer lugar, ¿qué es un README? Un archivo README contiene información genérica sobre el proyecto, como instalar, como configurar, como usar, licencia, contacto con el autor, bugs, etc… Para más detalles esta el artículo de la wikipedia (en inglés, el artículo en español es bastante malo).

Bien, pues GitHub te recomienda poner un archivo README en la raíz de tu proyecto, y para ello puedes usar varios lenguajes de maquetado. Un lenguaje de maquetado es un archivo de texto plano (normal y corriente) pero que usa una síntaxis especial y específica para poder pasarle una herramienta y convertirlo a HTML. Yo voy a utilizar Markdown.

La gracia viene en que voy a crear un archivo README.markdown y GitHub lo podrá convertir al vuelo en un HTML, de esta manera el que utilice un navegador web para explorar GitHub podrá ver una descripción de mi proyecto en HTML, que siempre queda mejor que en texto plano.

Como ya digo, voy a crear el README del proyecto PruebaGit y voy a incluir los siguientes datos: Descripción del proyecto, como instalar y contacto con el autor, con esos tres apartados creo que ya es suficiente.

Click con el botón derecho en PruebaGit > New… > File > Name: README.markdown. Con esto se abre el archivo en blanco.

A continuación voy a ir describiendo como voy usando el lenguaje Markdown para darle formato al texto:

  1. Título: Prueba Git. Para forzar que sea el primer encabezado, lo subrayo con =
  2. Para definir otro párrafo, basta con dejar una línea en blanco, o solo rellena con espacios o con tabuladores (y supongo que cualquier combinación de ellos).
  3. Instalación: Éste epígrafe es de tipo título 2, así que lo subrayo con -
  4. Los items de instalación son una lista, basta con comenzar cada item con -, * ó +. Con esto he tenido problemas (como se puede ver en el repositorio). La lista tiene que estar separada del párrafo anterior por una línea en blanco, si no, no la estima como válida.
  5. Contacto, de nuevo título 2, así que se subraya con -
  6. Enlaces: Los enlaces funcionan poniendo entre corchetes el título del enlace y a continuación, entre paréntesis, la url del enlace: [AguasNegras](http://www.aguasnegras.es/blog/?p=201).
  7. Los enlaces de mail, igual, título entre corchetes y entre paréntesis la dirección de mail: [agustinventura](http://www.aguasnegras.es/blog/?p=201)

Y eso es todo, los resultados se pueden ver aquí. Como detalle curioso e interesante, podemos decir que GitHub automáticamente te muestra el README en la página principal del repositorio del proyecto (ejemplo).

17 agosto, 2011
por Agustín Ventura
Sin comentarios

Localización en Android

En general en mi trabajo soy fanático de la internacionalización (i18n) y la localización (L18n), aunque la aplicación que este en desarrollo ni siquiera vaya a ser traducida nunca a ningún otro idioma.

El motivo es sencillo, si uso localización, cuando necesite poner un botón “Buscar” lo pondré en un archivo, por lo que al final todos los botones de “Buscar” de la aplicación apuntarán al mismo archivo (al mismo recurso). Ya se sabe que los clientes son caprichosos y es más que posible que pidan que ese “Buscar” se cambie por un “Encontrar”… y ahí es donde esta la potencia de la localización, basta con cambiar la palabra en el archivo adecuado. Se puede argüir que gracias a los IDEs modernos es sencillo hacer un search & replace all, pero creo que la opción de cambiar directamente lo que deseas en un archivo gana por goleada. Aparte, en lo personal, me dá mucha rabia que los informáticos en general tengan la duplicación de código por el anticristo (o así debiera ser) pero no tengan nada en contra de la duplicación sin medida de literales de textos.

En Android, el concepto de “Recurso” no incluye tan solo los textos, sino también, como dice la documentación: cadenas de texto, layouts, sonidos, gráficos y cualquier otro conjunto de datos estáticos. Cuando la aplicación se ejecuta, Android escoge automáticamente de entre todos los recursos provistos, el que mejor se adapta, no solo teniendo en cuenta el idioma, sino también la orientación del dispositivo, tamaño de pantalla, etc…

Por defecto, al desarrollar una aplicación se crean una serie de recursos por defecto, en el tutorial del bloc de notas ya ví algunos. En general es cualquier cosa dentro del directorio res/ y en ese caso vi las cadenas de texto (res/values/strings.xml) y los layouts (res/layout/notes_list.xml, res/layout/note_edit.xml, etc…). La documentación recomienda que los recursos por defecto sean siempre completos, es decir, si mi strings.xml tiene siete cadenas de texto, las mismas debe tener el que se usa por defecto, ya que si cambio el locale del dispositivo del español al inglés, la aplicación se cerrará inesperadamente al no encontrar el recurso. Una buena estrategia, aunque suene extraña, es desarrollar la aplicación enteramente en inglés usando el string.xml, y después copiar y pegar al archivo localizado y traducir. Todo de una tacada.

Vale, muy bien la teoría, excelente, pero, ¿cómo traduzco el tutorial del bloc de notas?. Pues un poco más de teoría. En el caso de los textos, si quiero un strings.xml en español, iría en el directorio /res/values-es/strings.xml. Ah, perfecto, ¿y si quiere español de España y de Perú? En ese caso, tendremos dos directorios: /res/values-es-rES y /res/values-es-rPE. La primera parte (el “es”) viene dado por el código de lenguaje según la ISO 639-1 (atención, la 1, no la 2, es de dos letras, no de tres. Lo siento por catalanes y demás afectados), después tendremos como parte fija el guión y la r (-r) y el código de región según la ISO 3166-1-alpha-2.

Vale, pero eso es lo que se refiere a lenguajes, ¿qué pasa si me interesa tener unos gráficos para pantallas grandes y otros para pantallas pequeñas? Pues que los gráficos por defecto estarán en /res/drawable, los de pantallas grande en /res/drawable-large y los de pantalla pequeña en /res/drawable-small, se puede consultar en la Tabla 2, aquí. Un momento… ¿y si quiero textos en español para dispositivos con pantalla grande? Pues eso: /res/values-es-rES-large… hala, toma ya. Pero… ¿y si tengo definidos textos en español y textos para pantalla grande en dos archivos distintos (res/values-es-rES y res/values-large) y usa la aplicación alguien con un Android con pantalla grande y en español? Pues hay unas reglas de resolución de recursos, pero en general, según la documentación, casi siempre mandan las locales.

Una vez visto esto, la guía dá una serie de recomendaciones:

  1. Proveer siempre los recursos por defecto, para evitar petes como ya he visto antes.
  2. En vez de definir un layout por idioma, usar un único layout, pero que sea flexible, que tenga capacidad de expandirse o contraerse y de controlar su tamaño programáticamente.
  3. Traducir únicamente lo imprescindible, si la aplicación por defecto esta en español de España, y quiero aportar también español de México, puede ser que no tenga que traducir todas las frases sino simplemente aquellas que incluyan por ejemplo “coche” y cambiarlo por “auto”.

Y ahora, al lío. Traducción del bloc de notas:

  1. En el proyecto Notepadv3, creo dentro de la carpeta /res la carpeta values-es-rES.
  2. Click con el botón derecho encima de la carpeta recién creada, New… > Android XML File.
  3. En nombre del archivo, usamos strings.xml y veremos que al crearlo dentro de esta carpeta nos aparece como escogidos los calificadores de pais y región.
  4. Click en Finish
Creación de strings.xml para es-ES

Creación de strings.xml para es-ES

Ahora a copiar y traducir:

  1. Abro el strings.xml original y copio todas las etiquetas <string>
  2. Las pego en mi nuevo strings.xml
  3. Traduzco.
  4. Arranco el emulador y cierro la aplicación para ponerlo en español (tecla home > tecla menu > settings > language & keyboard > select language > Español).
  5. Vuelvo a arrancar la aplicación… et voilà!
Bloc de Notas traducido al español

Bloc de Notas traducido al español

Y eso es todo, si quieres los fuentes del proyecto, los tienes en GitHub.

GitHub

10 agosto, 2011
por Agustín Ventura
Sin comentarios

Tutorial de Bloc de Notas para Android – Parte 7

Actualmente, la aplicación de bloc de notas deja mucho que desear, no solo con respecto al aspecto gráfico o usabilidad de la misma, sino que además no esta integrada en el ciclo de vida.

Una Activity tiene un ciclo de vida determinado y el programador se tiene que ajustar a él. Por ejemplo, si se arranca la aplicación y se le da a crear o editar una nota y a continuación al botón de atrás, la Activity se cierra inesperadamente. Para evitar este tipo de cosas, el tutorial propone dos soluciones:

  1. Integrar la Activity NoteEdit en el ciclo de vida de una Activity de Android.
  2. Mover toda la lógica de acceso a base de datos necesaria a NoteEdit (si se pulsa el botón atrás, se disparará el onPause() que se encargará de salvar la nota a la base de datos)

En general esto implica algunas funciones nuevas (básicamente, los callbacks del ciclo de vida) y movimiento de código de un lado a otro. A ello.

Con el nuevo enfoque (acceso a la base de datos en NoteEdit), se hace inútil pasar todos los datos de la nota a NoteEdit, basta con pasar el mRowId. Si mRowId es null, será una creación de nueva nota, en otro caso será una edición y se cargarán los datos de la base de datos. Borro en NoteEdit.java las líneas 46 y 47 y de la 50 a la 55.

Creo también una instancia de clase de NotesDbAdapter y se inicializa en el onCreate, justo después del super.onCreate().

Ahora viene la obtención del mRowId, se sigue este orden:

  1. Si la Activity estaba en ejecución anteriormente, habrá una instancia salvada, que se habrá pasado. En este caso se obtiene de esa instancia.
  2. Si no, es posible que el Intent traiga el mRowId, habrá que comprobarlo.
  3. Por último, puede ser que no venga de ninguna de las maneras y que, por tanto, sea un alta.

Sustituyo el código de las líneas 46 a 50 por este:

//Si la aplicación no tiene estado salvado, mRowId será nulo. En caso de tenerlo, se recupera.
mRowId = (savedInstanceState == null) ? null :
    (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID);
     //Si mRowId vale nulo, es que no había instancia salvada del estado de la aplicación
     if (mRowId == null) {
        //Se comprueba si mRowId viene en el Intent
        Bundle extras = getIntent().getExtras();
         //Aún así puede ser que no venga y entonces valdrá nulo, siendo un alta de nueva nota
         mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
                     : null;
     }

Nota de interés, en el primer caso se usa getSerializable porque devuelve un Object (una instancia de Serializable, más concretamente), mientras que getLong devuelve un long (el tipo primitivo), así que no serviría para hacer la comparación con null (getLong nunca podrá devolver null). Hmm… si embargo mRowId es de tipo Long, luego cuando en la última línea se hace ese getLong, en caso de venir (una edición), hay que hacer un autoboxing. No sé en la Dalvik VM, pero en Java “vanilla” el uso indiscriminado de autoboxing es una pérdida de rendimiento bastante gorda, habrá que estar pendiente de ello…

Una vez accedido y cargado el mRowId (bien del Intent, bien del savedInstance), es hora de cargar los campos (o no, claro), para ello se invoca a un nuevo método llamado populateFields() (implementación más adelante).

En lo que respecta al onClickListener, queda tal que así:

confirmButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                setResult(RESULT_OK);
                finish();
            }
});

A por ese populateFields… el tema es muy sencillo, si mRowId es distinto de nulo, habrá que cargar la nota desde la base de datos y establecer los valores de los textos:

private void populateFields() {
        if (mRowId != null) {
            Cursor note = mDbHelper.fetchNote(mRowId);
            startManagingCursor(note);
            mTitleText.setText(note.getString(
                        note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
            mBodyText.setText(note.getString(
                    note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
        }
}

Aquí pasa algo interesante, para adaptar la aplicación totalmente al ciclo de vida, tendríamos que implementar un método onPause() que entre otras cosas, liberase el cursor, y en el onResume() volver a abrirlo, etc… en general, gestionar el recurso del Cursor. Bueno, pues como ese caso es bastante, común, para eso se utiliza el startManagingCursor, se encarga de acoplar el ciclo de vida del cursor… y por cierto, esta deprecado… otra guasita del tutorial, ya me dá la risa floja.

Sigo, ahora hay que implementar el onSaveInstanceState, que es llamado por el framework Android antes de matar la aplicación. Va a hacer dos tareas, guardar la nota en la base de datos (creando una nueva o actualizando) y guardar el mRowId en el savedInstanceState para que lo pueda usar el onCreate.

@Override
protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        saveState();
        outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId);
}

Siguiente, el onPause(), en este caso, solo hay que guardar en base de datos (ya que no se va a matar la Activity):

@Override
protected void onPause() {
        super.onPause();
        saveState();
    }

Y el onResume(), más de lo mismo, no se ha destruido la aplicación, asi que basta con leer los valores de la base de datos (es decir, invocar el populateFields()). Pregunta, ¿es necesario? Si la Activity no se ha llegado a matar, ¿hace falta volver a cargar los valores? Me lo dejo como ejercicio.

@Override
    protected void onResume() {
        super.onResume();
        populateFields();
    }

Y el saveState():

private void saveState() {
        String title = mTitleText.getText().toString();
        String body = mBodyText.getText().toString();
 
        if (mRowId == null) {
            long id = mDbHelper.createNote(title, body);
            if (id &gt; 0) {
                mRowId = id;
            }
        } else {
            mDbHelper.updateNote(mRowId, title, body);
        }
    }

Lo único a reseñar es que si mRowId es null y la inserción es correcta (id > 0), se carga el mRowId, es decir, cuando se recargue la Activity o se vuelva a ejecutar el onCreate, será una edición, no un alta.

Ahora toca limpiar el Notepadv3, quitando llamadas varias a la base de datos. En primer lugar, el onActivityResult, ya simplemente hay que refrescar la vista, por si ha habido posibles cambios (hmm… esto también es muy optimizable):

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    fillData();
}

Igual para el onListClickItem, ya no hace falta acceder a base de datos, y el mRowId viene dado por el id que recibe el método:

@Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        Intent i = new Intent(this, NoteEdit.class);
        i.putExtra(NotesDbAdapter.KEY_ROWID, id);
        startActivityForResult(i, ACTIVITY_EDIT);
    }

Por último, el Cursor ya no hace falta que sea una variable de instancia de la Activity, sino que puede ser una variable local del fillData.

Bueno, pues con esto termina el tutorial de Google que empecé hace ya más de un mes… no ha estado mal, quizás me ha faltado constancia. Me queda el detalle de examinar más de cerca lo que he apuntado arriba del ciclo de vida… y una vez hecho eso, habrá que buscar una idea de aplicación a desarrollar (ya tengo una primera…). Aparte, también le echaré un vistazo al libro de Maestros del Web, que tiene una pinta bastante buena.

creo que cuando lo haya leído, haré un post de conclusiones sobre Android así en general, aunque mi primera impresión es muy positiva. Si alguien ha seguido la serie hasta aquí, gracias por leerme :) .