viernes, 27 de noviembre de 2009

Configuración de Log4j en una aplicación web

Log4j es una librería que permite guardar registros informativos sobre el funcionamiento de una aplicación. En este artículo vamos a ver cómo configurar Log4j para su uso en una aplicación web.

En primer lugar vamos a crear en Eclipse un proyecto war Maven que se llame PruebaLog4jWeb. Crearemos también una página de inicio index.jsp y desplegaremos la aplicación en un servidor. La URL de la aplicación será http://localhost:8080/PruebaLog4jWeb/.

Añadimos la dependencia a la API de servlets para poder trabajar en nuestro proyecto con sus clases. Esta librería no hace falta desplegarla en el servidor, puesto que éste tendrá su propia implementación de esta API:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>

Añadimos la dependencia a Log4j en el pom.xml del proyecto:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>

Ahora vamos a crear el siguiente servlet:
package com.roldan.log4j;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

public class ServletPrueba extends HttpServlet {

private static final long serialVersionUID = 1L;

static Logger logger = Logger.getLogger(ServletPrueba.class);

public ServletPrueba() { }

protected void doGet(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

logger.debug("Se ha llamado al servlet mediante un GET.");

execute(request, response);
}

protected void doPost(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

logger.debug("Se ha llamado al servlet mediante un POST.");

execute(request, response);
}

protected void execute(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

logger.info("Se redirige a la página de inicio.");

RequestDispatcher rd =
request.getRequestDispatcher("index.jsp");
rd.forward(request, response);
}
}

En él hemos creado un logger estático de Log4j que usamos en los distintos métodos del servlet para mostrar logs con distinto grado de importancia.

Para configurar la salida de los logs tenemos que crear un archivo log4j.properties, donde definiremos cómo queremos que se muestren los logs. Vamos a mostrar los logs de nuestra aplicación tanto por consola como en un archivo:
log4j.logger.com.roldan.log4j=debug, stdout, file

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=example.log

log4j.appender.file.MaxFileSize=100KB
# Keep one backup file
log4j.appender.file.MaxBackupIndex=1

log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%p %t %c - %m%n

Por último, debemos inicializar Log4j dentro de cada aplicación web, para que cada aplicación pueda trabajar satisfactoriamente de modo independiente. Para ello, vamos a incluir en la aplicación el siguiente servlet de inicialización:
package com.roldan.log4j;

import org.apache.log4j.PropertyConfigurator;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Log4jInit extends HttpServlet {

public void init() {
String prefix = getServletContext().getRealPath("/");
String file = getInitParameter("log4j-init-file");

if(file != null) {
PropertyConfigurator.configure(prefix+file);
}
}

public void doGet(HttpServletRequest req, HttpServletResponse res) {}
}

E incluiremos la siguiente configuración en el archivo web.xml:
<servlet>
<servlet-name>log4j-init</servlet-name>
<servlet-class>com.roldan.log4j.Log4jInit</servlet-class>
<init-param>
<param-name>log4j-init-file</param-name>
<param-value>WEB-INF/log4j.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

Cuando redesplegamos y ejecutamos la aplicación llamando al servlet mediante la URL http://localhost:8080/PruebaLog4jWeb/ServletPrueba se muestran los siguientes logs en la consola:
DEBUG [http-8080-2] (ServletPrueba.java:23) 
- Se ha llamado al servlet mediante un GET.
INFO [http-8080-2] (ServletPrueba.java:39)
- Se redirige a la página de inicio.

Referencias:
http://logging.apache.org/log4j/1.2/index.html
http://logging.apache.org/log4j/1.2/manual.html

miércoles, 18 de noviembre de 2009

Configuración de EhCache en Spring

En este artículo vamos a ver cómo se puede acceder a los datos almacenados en una base de datos a través de una plantilla JDBC de Spring. Para mejorar el rendimiento en este acceso a base de datos introduciremos también una caché usando EhCache que permita almacenar las consultas ya obtenidas evitando el acceso a la base de datos.

En primer lugar, creamos en Eclipse un proyecto Maven e incluimos las dependencias necesarias en el archivo pom.xml del proyecto:
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roldan.spring</groupId>
<artifactId>PruebaCache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
<dependency>
<groupId>org.springmodules</groupId>
<artifactId>spring-modules-cache</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
</project>

Las dependencias necesarias para nuestro proyecto son:
  • El framework Spring.
  • Log4j para sacar logs informativos sobre la ejecución de la aplicación.
  • El conector a base de datos MySql.
  • El módulo de cache del proyecto spring-modules, que permite integrar la herramienta EhCache con Spring de forma sencilla.
  • La herramienta de cacheo EhCache.

Posteriormente hemos creado una base de datos (pruebacache) con una única tabla (elementos) cuyos elementos serán recogidos por las consultas que realiza la aplicación:

El objeto al que se mapean los registros de esta tabla está definido en la siguiente clase:
package com.roldan.spring.model;

public class Elemento {

private int id;
private String descripcion;
private int grupo;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public int getGrupo() {
return grupo;
}
public void setGrupo(int grupo) {
this.grupo = grupo;
}
}

A continuación se muestra el archivo de configuración de Spring, spring-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ehcache="http://www.springmodules.org/schema/ehcache"
xmlns:jdbc="http://www.springmodules.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springmodules.org/schema/ehcache
http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd">
<bean id="datasource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/pruebacache"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"/>
</bean>
<bean id="elementoDao" class="com.roldan.spring.dao.ElementoDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<ehcache:config configLocation="classpath:ehcache.xml"/>
<ehcache:proxy id="proxyElementos" refId="elementoDao">
<ehcache:caching methodName="get*" cacheName="cacheElementos"/>
</ehcache:proxy>
</beans>

En este archivo se configuran los siguientes beans:
  • Un datasource sencillo a nuestra base de datos.
  • Una plantilla de acceso a JDBC de Spring.
  • Un objeto de acceso a datos para recuperar elementos de la base de datos.
  • Un bean proxy que permite incorporar cacheo en las llamadas al bean DAO.

En cuanto al los beans datasource y jdbcTemplate, no hay que hacer más que seguir las instrucciones de configuración de Spring. El bean elementDao pertenece a la clase ElementDaoImpl que implementa la interfaz ElementDao. A continuación vemos la implementación de la clase ElementDaoImpl:
package com.roldan.spring.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.roldan.spring.Prueba;
import com.roldan.spring.model.Elemento;

public class ElementoDaoImpl implements ElementoDao {

private static Logger logger =
Logger.getLogger(ElementoDaoImpl.class);

public static final String RECUPERAR_ELEMENTOS =
"SELECT * FROM ELEMENTO WHERE GRUPO = ?";
public static final String RECUPERAR_TODOS =
"SELECT * FROM ELEMENTO";

public List getElements(int grupo) {
List matches = jdbcTemplate.query(
RECUPERAR_ELEMENTOS,
new Object[] {Integer.valueOf(grupo)},
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum)
throws SQLException {
Elemento elemento = new Elemento();
elemento.setId(rs.getInt(1));
elemento.setDescripcion(rs.getString(2));
elemento.setGrupo(rs.getInt(3));

logger.info("Se mapea el elemento " + elemento.getId());

return elemento;
}
});
return matches;
}

public List getAllElements() {
List matches = jdbcTemplate.query(RECUPERAR_TODOS,
new Object[] {},
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum)
throws SQLException {
Elemento elemento = new Elemento();
elemento.setId(rs.getInt(1));
elemento.setDescripcion(rs.getString(2));
elemento.setGrupo(rs.getInt(3));

logger.info("Se mapea el elemento " + elemento.getId());

return elemento;
}
});

return matches;
}

JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}

Tras esto, sólo nos queda revisar la configuración de EhCache en Spring, realizada a través del módulo spring-modules-cache. En el archivo spring-config.xml vemos que se ha definido que el archivo de configuración de EhCache es ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<defaultCache
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"/>
<cache name="cacheElementos"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="1"
memoryStoreEvictionPolicy="LFU"/>
</ehcache>

En este archivo se configura una cache por defecto, que es obligatoria, y una cache para las aplicación. En esta caché se pueden guardar hasta 300 elementos, y cada elemento se corresponde con una llamada a un método con unos determinados parámetros. Si se repiten llamadas al mismo método con los mismos valores de parámetros, el resultado de la llamada estará cacheado y no será necesario ir a la base de datos a recuperarlo.

Por ejemplo, si ejecutamos la siguiente clase de prueba:
package com.roldan.spring;

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.roldan.spring.dao.ElementoDao;
import com.roldan.spring.model.Elemento;

public class Principal {

public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("spring-config.xml");

ElementoDao elementoDao = (ElementoDao) ctx.getBean("proxyElementos");

List elementos = elementoDao.getElements(1);
for (Elemento elemento : elementos) {
System.out.println(elemento.getDescripcion());
}

elementos = elementoDao.getAllElements();
for (Elemento elemento : elementos) {
System.out.println(elemento.getDescripcion());
}

elementos = elementoDao.getElements(1);
for (Elemento elemento : elementos) {
System.out.println(elemento.getDescripcion());
}
}
}

Se obtienen los siguientes logs:

DEBUG - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [com.roldan.spring.dao.ElementoDaoImpl@2a6f16]
DEBUG - Finished creating instance of bean 'proxyElementos'
DEBUG - Returning cached instance of singleton bean 'proxyElementos'
DEBUG - Attempt to retrieve a cache entry using key <1130820531|30562044> and cache model
DEBUG - Retrieved cache element
DEBUG - Executing prepared SQL query
DEBUG - Executing prepared SQL statement [SELECT * FROM ELEMENTO WHERE GRUPO = ?]
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost/pruebacache]
INFO - Se mapea el elemento 1
INFO - Se mapea el elemento 3
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Attempt to store the object <[com.roldan.spring.model.Elemento@7109c4, com.roldan.spring.model.Elemento@1385660]> in the cache using key <1130820531|30562044> and model
DEBUG - Object was successfully stored in the cache
Primer elemento
Tercer elemento
DEBUG - Attempt to retrieve a cache entry using key <23191881|23191477> and cache model
DEBUG - Retrieved cache element
DEBUG - Executing prepared SQL query
DEBUG - Executing prepared SQL statement [SELECT * FROM ELEMENTO]
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost/pruebacache]
INFO - Se mapea el elemento 1
INFO - Se mapea el elemento 2
INFO - Se mapea el elemento 3
INFO - Se mapea el elemento 4
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Attempt to store the object <[com.roldan.spring.model.Elemento@4ecfdd, com.roldan.spring.model.Elemento@30d82d, com.roldan.spring.model.Elemento@c09554, com.roldan.spring.model.Elemento@18bf072]> in the cache using key <23191881|23191477> and model
DEBUG - Object was successfully stored in the cache
Primer elemento
Segundo elemento
Tercer elemento
Cuarto elemento
DEBUG - Attempt to retrieve a cache entry using key <1130820531|30562044> and cache model
DEBUG - Retrieved cache element <[com.roldan.spring.model.Elemento@7109c4, com.roldan.spring.model.Elemento@1385660]>
Primer elemento
Tercer elemento

Se pueden apreciar los siguientes pasos:
  • En la primera llamada se buscan todos los elementos que pertenecen al grupo 1. La llamada pasa por el proxy, que trata de buscar el resultado de esta llamada en la cache y, como no lo encuentra, acude a base de datos.
  • En la segunda llamada se buscan todos los elementos. Esta llamada también pasa por el proxy, que trata de buscar el resultado de esta llamada en la cache y, como no lo encuentra, acude a base de datos.
  • En la tercera llamada se buscan de nuevo todos los elementos que pertenecen al grupo 1. Al pasar por el proxy, éste reconoce que el resultado de la llamada está en la caché y que no hace falta acudir a la base de datos, ahorrándose la consulta.

Con esto, tenemos ya un ejemplo de cómo utilizar una cache en las consultas a base de datos en Spring.

Referencias:
http://www.dosideas.com/wiki/EhCache_Con_Spring

jueves, 12 de noviembre de 2009

La Deuda Técnica

¿Quién no se ha encontrado en el siguiente dilema? Tenemos que añadir una nueva funcionalidad al sistema y tenemos prisa. Podemos hacerlo rápido y de forma no óptima, más enrevesado o menos eficiente, satisfaciendo las exigencias de entrega. O podemos (si es que podemos…) saltarnos estas exigencias y sentarnos a pensar y hacerlo mejor, con diseño más claro y mantenible.

La Deuda Técnica es una metáfora ideada por Ward Cunningham que ayuda a pensar sobre este problema. Viene a decir que en caso de hacer las cosas rápido y de manera chapucera se incurre en una “deuda” que vamos a tener que pagar en el futuro, en forma de esfuerzo de desarrollo, como consecuencia de postergar el buen diseño en la implementación. Y a medida que se siga postergando este esfuerzo y el desarrollo deficiente se siga recubriendo con nuevos desarrollos que lo enmascaren o lo usen, esta deuda será cada vez más elevada y difícil de pagar.

No se quiere decir que no sea sensato incurrir en esta deuda. En ciertas ocasiones es una decisión acertada asumir esta deuda para resolver una situación determinada en un proyecto. Sin embargo, hay que tener en cuenta las repercusiones que puede tomar en el futuro y hay que tratar de satisfacer esta deuda cuanto antes para evitar que crezca desmesuradamente, es decir, que un mal planteamiento en una funcionalidad de la aplicación comprometa la buena marcha del proyecto en el futuro.

Sobre esta idea, Martin Fowler ha desarrollado el “Cuadrante de la Deuda Técnica”. Según él, una deuda técnica asumida en un proyecto puede clasificarse de dos formas:
  • Prudente o imprudente.

  • Advertida o inadvertida.

La combinación de estas clasificaciones produce cuatro situaciones distintas:
  • Deuda prudente y advertida: Se produce cuando somos conscientes de que estamos asumiendo un mal diseño para salir de una situación puntual, como una entrega. Posteriormente se debe evaluar la conveniencia de “pagar la deuda técnica” solucionando esta carencia en función del interés que acarree.

  • Deuda prudente e inadvertida: Se produce cuando, con el paso del tiempo, se descubren cómo se deberían haber hecho las cosas. Inevitable, ya que según avanza el proyecto, se aprende de él. Llegado el momento, también se debe evaluar si resulta interesante mejorar el diseño con lo que hemos aprendido o si esto implicaría un esfuerzo demasiado alto.

  • Deuda imprudente y advertida: Se deshecha el diseño porque se pretende acelerar el desarrollo. Según la “Hipótesis de Resistencia al Diseño”, esto se puede lograr hasta un punto determinado del proyecto en el que deja de ser posible conseguir un beneficio de obviar el diseño porque la calidad del código se degrada.

  • Deuda imprudente e inadvertida: No se conocen las técnicas de diseño ni se es consciente de que se están tomando decisiones incorrectas.

Conviene saber en qué situación nos encontramos. Si bien las tres primeras situaciones se pueden asumir en un momento determinado dentro de un proyecto, la última resulta mucho más peligrosa, porque implica no saber hacia dónde se dirige el proyecto.

Referencias:

La Deuda Técnica
El Cuadrante de la Deuda Técnica
La Hipótesis de Resistencia al Diseño

viernes, 6 de noviembre de 2009

EclEmma: Visualizar la cobertura de los tests unitarios en Eclipse

Hace algún tiempo publiqué un post sobre herramientas de calidad del código. A las que yo publiqué, Dirty Affairs añadió otra, EclEmma, a la cual he estado echando un vistazo.

EclEmma es una herramienta para Eclipse que permite visualizar la cobertura del código de los tests unitarios que hemos hecho. Existen otras herramientas como Cobertura, que generan informes, pero que no permiten visualizar esta informacuón de forma gráfica directamente en Eclipse.

EclEmma se instala en Eclipse de forma sencilla, desde Help -> Software Updates -> Available Software -> Add Site, se añade el sitio http://update.eclemma.org/ y pasamos a realizar la instalación del plugin.

Una vez instalado el plugin y reiniciado Eclipse, aparece una nueva opción en la barra de herramientas:

A través de este botón, podemos lanzar los tests de forma que EclEmma analizará qué parte del código se está cubriendo en ellos. Veamos un ejemplo práctico.

He creado una clase OperadorAritmético, que realiza las operaciones de suma y division:
package com.roldan.tests;

public class OperadorAritmetico {

public static int suma(int a, int b) {
return a + b;
}

public static int division(int a, int b) throws Exception {
if(b==0) {
throw new Exception();
}
return a / b;
}
}

Para probar esta clase he creado una clase de test que, en principio, solo prueba la suma:
package com.roldan.tests;

import org.junit.Assert;
import org.junit.Test;

public class OperadorAritmeticoTest {

@Test
public void suma() {

int a = 5;
int b = 3;

int suma = OperadorAritmetico.suma(a, b);

Assert.assertEquals(8, suma);
}
}

Ahora lanzamos las pruebas para el proyecto, sitándonos en la raíz del proyecto y yendo al botón de EclEmma antes mostrado, seleccionamos la opción Coverage as -> JUnit Test:

Vemos como las instrucciones de la clase OperadorAritmetico has sido coloreadas, con verde aquellas que han sido cubiertas en las pruebas y con rojo aquellas que no. La pestaña Coverage muestra el informe de cobertura del proyecto.

Ahora queremos arreglar un poco esta situación y hacer un prueba para la división:
@Test
public void division() {

int a = 8;
int b = 4;

int division;
try {
division = OperadorAritmetico.division(a, b);
Assert.assertEquals(2, division);
} catch (Exception e) {
Assert.fail();
}
}

Si ahora volvemos a realizar las pruebas, vemos que la situación se ha mejorado, aunque todavía queda código sin cubrir:

De esta forma, podemos ir mejorando progresivamente la cobertura de las pruebas para asegurar el buen funcionamiento de nuestro código.

Referencias:
http://tratandodeentenderlo.blogspot.com/2009/10/herramientas-de-analisis-de-calidad-del.html
http://www.eclemma.org/
http://cobertura.sourceforge.net/

jueves, 5 de noviembre de 2009

Lecturas anteriores

DESACTUALIZADO: Puedes seguir mis lecturas en Goodreads

En esta entrada simplemente se listan los libros que he leído y que vaya leyendo, por tanto, estará en constante actualización.

También espero que sirva para recibir recomendaciones sobre estos libros o sobre otros que puedan resultar interesantes.

martes, 3 de noviembre de 2009

Próximas lecturas

DESACTUALIZADO: Puedes seguir mis lecturas en Goodreads

Esta entrada no es más que una declaración de intenciones, una recopilación de los libros que tengo previsto leer proximamente. Por tanto, estará en constante actualización.

También espero que sirva para recibir recomendaciones sobre estos libros o sobre otros que puedan resultar interesantes.

  • Growing Object-Oriented Software Guided by Tests (Steve Freeman, Nat Pryce)

  • Patterns of Enterprise Application Architecture (David Fowler)

  • Head First Design Patterns (Eric T Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra)