Archive for July, 2008

ECF mola

Muchas gracias a Christian López y a Jhon Oviedo por ayudarme a probar el Eclipse Collaboration Framework tal y como comenté en mi entrada anterior.

La experiencia ha sido un tanto agridulce (seguramente porque habíamos puesto muchas expectativas en él). El caso es que funciona (nadie nos ha engañado), pero tiene los siguientes problemillas:
  1. Sólo se puede compartir un fichero cada vez.
  2. No parece que se puedan compartir todo tipo de ficheros (no lo tengo muy claro, pero no he podido compartir un pom.xml y sospecho que es por algo relacionado con el tipo de editor -que es diferente al editor que se usa para los .java).
  3. El fichero compartido en realidad no lo es tanto, es decir, se transfiere al espacio de trabajo del “receptor” pero el que tiene todas las capacidades de compilar y demás es el que inicia la colaboración. Por tanto, hay cosas como las opciones de refactorizar y otras parecidas que no se pueden usar “como si estuvieramos juntos”.
  4. También hemos probado el “workspace de colaboración” y ha resultado poco satisfactorio porque se parece más bien a un IRC que te permite enviar ficheros y pantallazos con cierta facilidad, pero no es un “workspace compartido”.

De todos modos, me parece que el camino abierto es sencillamente “estelar” (aunque de momento no parezca que haya ningún plan previsto para seguir desarrollando).

Tags: ,

Necesito compartir mi editor

Por favor, por favor, por favor. Si alguien tiene Eclipse 3.4 (Ganymede) y quiere probar conmigo el ECF (Eclipse Communication Framework), por favor que se instale el paquete entero de Communications (ECF Application y ECF Core API) y que se ponga en contacto conmigo para probarlo tal y como hacen en el screencast del que hablaba en mi entrada anterior.

Más detalles:

P.S.
Lo siento, no tengo Skype en mi portátil aún. Si puedo, lo instalo también hoy mismo.

Tags: ,

¿Quién dijo que programar en parejas es un desperdicio de recursos?

Estaba curioseando por los blogs de algunos miembros de la lista de Ecosistemas Software y me he encontrado con algo que seguro que va a revolucionar la manera de programar de una vez por todas.

Uno de los argumentos de los “managers” para rechazar la programación por parejas es que es un desperdicio de recursos. Pues bien, ahora ya no van a tener dónde esconderse… :-)

En cuanto lo haya probado, contaré cómo me ha ido.

¡¡Viva Eclipse!!

Tags: , ,

Spring, JPA y DBUnit (2)

Vaya, olvidé ayer explicar un detalle interesante (no imprescindible para los que ya conozcáis JPA). Pero creo que lo correcto es completar el artículo y dejarlo “hecho, hecho”.

Se trata de la clase principal de nuestro dominio en el ejemplo: la entidad User.

@Entity
public class User {

@Id
String id;

@Column(unique=true)
private String username;

@Column
private String name;

@Column
private String surname;

public User(String username, String name, String surname) throws UserInitializationException {
this.username = username;
this.name = name;
this.surname = surname;
if ( username == null ) {
throw new UserInitializationException("username is null");
}
if ( "".equals(username.trim())) {
throw new UserInitializationException("username is empty");
}
}

public String getUsername() {
return username;
}

public String getName() {
return name;
}

public String getSurname() {
return surname;
}

/**
* Changing someone's name can have side effects.
*
* name
*/
public void changeName(String name) {
this.name = name;
}

/**
* Changing someone's surname can have side effects.
*
* surname
*/
public void changeSurname(String surname) {
this.surname = surname;
}

public String getId() {
return this.id;
}

/**
* Private constructor only for JPA-use.
*/
private User() { }


}



Fijaos en dos detalles que sólo tienen que ver con la persistencia:

  • El atributo id (anotado convenientemente con para que el ORM se encargue de generarlo y todo eso) y su correspondiente getter. No tiene nada que ver con el dominio y, por tanto, si fueramos puristas no debería estar en esta clase sino en un DTO especializado. Sin embargo, JPA justamente nos viene a ayudar en esto para evitar esas clases utilitarias que no hacen más que enredarnos…
  • El constructor vacío que hemos dejado como privado tampoco tiene nada que ver con el dominio. De hecho, es el ORM el que lo necesita para instanciar las clases a partir de los datos en la BD. Por eso lo he dejado como privado, porque así evitamos la tentación de usar esta clase como DTO.

El resto es una clase del dominio: una entidad, tal y como la clasificaría Eric Evans y tal y como la hemos estereotipado con la anotación @Entity.

Tags: ,

De mayor quiero ser como Kent Beck

Dada mi nueva situación (perdonad el eufemismo, pero es la moda…), estos días estoy trabajando en mi CV. Para ello he visitado los perfiles que otros profesionales tienen en LinkedIn. Y en esta labor de cotilleo estaba cuando dí con el perfil del mismísimo Kent Beck (autor de libros como el muy recomendable “Implementation Patterns”, frameworks tan usados como JUnit o tendencias tan reconocidas como el Test-Driven Development). Mi admiración por él ha aumentado aún más cuando he leido su resumen. Tanto es así que lo he incorporado a mi propio perfil (sin quitarle ni una coma).

Espero que Mr. Beck me disculpe por el plagio, pero se trata, además de un homenaje, de una identificación total con cada una de sus palabras.

Tags: , ,

Spring, JPA y DBUnit

Como ahora tengo bastante tiempo libre, he empezado una aplicación para prototipar algunos conceptos que me interesa fijar. Así, he comenzado por resolver la arquitectura básica de persistencia. He decidido usar Spring y JPA (Hibernate). Y para las pruebas he querido usar también DbUnit para llenar una base de datos en memoria (H2).

Y como estaba bastante oxidado, estuve viendo las nuevas anotaciones en Spring 2 y me llamó la atención el estereotipo . Dado que encaja muy bien con el enfoque de “modelo rico” en el que quiero trabajar, he estado jugando con esto y ha quedado así.

Comenzaré por el test.

@ContextConfiguration(locations = {"classpath:/META-INF/dao-test-context.xml"})
public class UserRepositoryTest extends AbstractRepositoryTest {

@Autowired
private UserDAO userDAO;

@Test
public void testFindByUsername() {
User aUser = userDAO.findByUsername("rob");
assertEquals("0001", aUser.getId());
assertEquals("Robert", aUser.getName());
assertEquals("Smith", aUser.getSurname());
}



La anotación permite decir dónde están los ficheros de configuración de Spring (para nuestro ejemplo sólo usamos uno, pero se pueden poner todos los que se quieran). Con inyectamos el DAO que vamos a probar sin necesidad de declararlo en el xml.

El código de la clase abstracta de la que extendemos tiene la “fixture” de la base de datos usando DbUnit.


@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,TransactionalTestExecutionListener.class})
@Transactional
public class AbstractRepositoryTest {

private static String TEST_DATA_FILE = "src/test/resources/dbunit-test-data.xml";

/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());

@Autowired
private DataSource dataSource;

private DataSource getDataSource() {
return this.dataSource;
}

@BeforeTransaction
public void onSetUpInTransaction() throws Exception {
logger.info("*** Inserting test data ***");
// Use spring to get the datasource
DataSource ds = getDataSource();
Connection conn = ds.getConnection();
try {
IDatabaseConnection connection = new DatabaseConnection(conn);
DatabaseOperation.CLEAN_INSERT.execute(connection,
new FlatXmlDataSet(new FileInputStream(TEST_DATA_FILE),false));
} finally {
DataSourceUtils.releaseConnection(conn, ds);
logger.info("*** Finished inserting test data ***");
}
}

@AfterTransaction
public void onTearDownInTransaction() throws Exception {
// Delete the data
DataSource ds = getDataSource();
Connection conn = ds.getConnection();
try {
IDatabaseConnection connection = new DatabaseConnection(conn);
DatabaseOperation.DELETE.execute(connection, new FlatXmlDataSet(
new FileInputStream(TEST_DATA_FILE)));
} finally {
DataSourceUtils.releaseConnection(conn, ds);
logger.info("*** Finished removing test data ***");
}
}



El “truco” aquí consiste en la anotación y la inyección de DataSource (que está declarada en el xml de Spring).

El repositorio lo implementamos como un DAO, pero en vez de heredar de JpaDaoSupport (que es quizás lo más común cuando usamos Spring) lo implementamos independiente de Spring (aunque añadamos la anotación @Repository). Cuando usamos , el contenedor de Spring “sabe” cómo inyectar el EntityManager sin que tengamos que decirle nada explícitamente.


@Repository
public class JpaUserDAO implements UserDAO {

private EntityManager entityManager;

@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}

public EntityManager getEntityManager() {
return entityManager;
}

public User findByUsername(final String username) {
return (User) getEntityManager().
createQuery("from User where username = :username").
setParameter("username", username).
getSingleResult();
}





Como es JPA, es necesario el fichero META-INF/persistence.xml.


<persistence version="1.0">
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

<persistence-unit name="ACME_PU-TEST" transaction-type="RESOURCE_LOCAL">
<class>acme.core.users.User</class>
</persistence-unit>

</persistence>



Obsérvese que apenas hay que darle información. Tenemos que explicitar qué tenemos (podemos hacerlo en un fichero orm.xml, pero por simplicidad lo he dejado dentro).

Y finalmente, el pegamento de todo esto… el xml de Spring.


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">

<context:component-scan base-package="acme.core"/>
<context:annotation-config/>

<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="persistenceUnitName" value="ACME_PU-TEST"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:test_mem"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>

</beans>


Es importante señalar que hay dos partes en este xml (de hecho, los he fusionado para que resultara más compacto, pero realmente los tengo separados): los elementos del espacio de nombres “context” y “beans” respectivamente.

Las primeras son necesarias para poder usar y otras. Las otras son los únicos tres beans que necesitamos declarar en el xml (porque no son nuestros sino implementaciones que vienen con la librería de Spring).

Es importante declarar los tres y usar esos nombres para aprovechar la inyección automática “byName”.

Falta el xml con los datos para que DbUnit cree la tabla y rellene los datos de prueba (nuestra “fixture”).


<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<user id="0001" username="rob" name="Robert" surname="Smith" />
<user id="0002" username="rob2" name="Robert" surname="Jones" />
<user id="0003" username="peter" name="Peter" surname="Mullins" />
</dataset>



Espero que os sea útil. A continuación os explicaré cómo implementar una capa de servicios que use este repositorio de datos, con sus pruebas de aceptación (con Concordion, por supuesto) y todo… :-)

Tags: , , ,