jueves, julio 29, 2004

J2EE sin EJB

Finalmente tengo entre mis manos, desde la semana pasada, el libro J2EE Development without EJB de Rod Johnson y Juergen Hoeller, líderes en el desarrollo de Spring Framework.
Que decir sobre este libro que no se haya dicho ya, salvo que debe leerse, tanto si se desarrolla con EJBs como si se pertenece a la nueva ola de "EJBs sólo si es muy necesario". Es un excelente documento sobre buenas prácticas, a la vez que hace escuela sobre una nueva forma de ver J2EE: simple!. En definitiva, todo arquitecto y/o desarrollador debería leerlo.

Y no olviden que con la próxima versión 1.2 de Spring (Octubre/2004), ya casi tenemos un J2EE container por si solo, aunque no a todo el mundo le gusta oír esto.

miércoles, julio 14, 2004

Tiger - Autoboxing

Hasta ahora, la conversión entre tipos de datos primitivos y objetos es tediosa y propensa a errores. Por ejemplo, si queremos agregar una cantidad a un ArrayList, debemos crear un Integer (por ejemplo), porque el método add necesita un objeto:

  List cantidades = new ArrayList();
  cantidades.add(new Integer(7));


Pero en la nueva versión 1.5 de Java (o debería decir 5.0?), el compilador añade automáticamente el codigo para convertir el tipo primitivo en un objeto, por lo que ahora es perfectamente válido lo siguiente:

  List cantidades = new ArrayList();
  cantidades.add(7);


Este proceso se llama "boxing", y el proceso inverso, es decir convertir un objeto en un tipo de dato primitivo, "unboxing".

Si tuviéramos este método

  public void test(int val) {
    ...
  }


Podríamos utilizar

  Integer numero = new Integer(7);
  test(numero);


En lugar de

  Integer numero = new Integer(7);
  test(numero.intValue());


Esto es asi para los tipos boolean, byte, double, short, int, long y float, que son "convertidos" en Boolean, Byte, Double, Short, Int, Long y Float respectivamente.

martes, julio 13, 2004

Datos internacionalizados en Hibernate

Como resolver mediante UserTypes de Hibernate un típico caso de i18n, donde tenemos en una base de datos las descripciones de ciertos elementos en diferentes idiomas:

Internationalized data in Hibernate

Este ejemplo nos sirve también para pensar mas en los UserTypes para resolver situaciones donde los mappings son insuficientes.

martes, julio 06, 2004

Que Puedes Hacer Con EJB3 (primera parte)

La que sigue es una traducción del artículo What You Can Do With EJB3 (part one) de Gavin King, quien muy generosamente me ha permitido hacerla. También ha sido publicado en TheServerSide.com, donde hay una interesante discusión sobre el mismo.

Creo que merece la pena tener en castellano este tipo de artículos del fundador de Hibernate que, con una claridad meridiana, nos acerca al mundo real la especificación de EJB 3.0. A disfrutar!

Que Puedes Hacer Con EJB3 (primera parte) 05. Jul 2004, 19:51 by gavin@hibernate.org

Ahora que el primer borrador de EJB 3.0 está publicado, definitivamente es el momento para dejar atrás toda la política reciente, y comenzar a pensar lo que podemos hacer realmente con todo esto. EJB es el único estándar Java que se ocupa de la lógica de negocio del lado del servidor, y esto es fundamental para J2EE. El comité de especificación de EJB reconoce que EJB se ha quedo corto en alcanzar algunas de sus ambiciosas metas, y necesita una modernización. Esta modernización está muy enfocada en la facilidad de uso, principalmente mediante la simplificación de los requerimientos de los implementadores de beans. Sin embargo, el comité de especificación también ha identificado un número de mejoras funcionales críticas que facilitan el uso y la eliminación de ciertos anti-patrones J2EE.

Afortunadamente, la comunidad Java conoce ahora mucho mas los problemas relacionados a la construcción de aplicaciones web y empresariales que hace cinco años. Hemos "aprendiendo haciendo". Una forma poderosa, fácil de utilizar y estándar de construir objetos de negocio del lado del servidor ahora esta mas a nuestro alcance!

El comité de especificación pensó mucho como simplificar ciertos casos de usos extremadamente comunes, especialmente dos casos que especificaré a continuación:

Actualizando datos

1. recuperar un dato
2. mostrarlo en pantalla
3. aceptar la entrada del usuario
4. actualizar el dato
5. comunicar el éxito al usuario

Ingresando nuevos datos

1. recuperar un dato
2. mostrarlo en pantalla
3. aceptar la entrada del usuario
4. crear un nuevo dato, con una asociación al primer dato
5. comunicar el éxito al usuario

Cada uno de estos casos involucra dos ciclos request/response de aplicación completos. Ambos demuestran que el bloqueo optimista es esencial en una aplicación que requiere alta concurrencia (ambos casos necesitarían ser implementados como una sola comunicación atravesando dos transacciones ACID de base de datos/JTA distintas).

Un caso de uso adicional muy común, que es especialmente difícil de hacer en EJB 2.1, dadas las limitaciones del lenguaje de consultas es el siguiente:

Listando datos

1. recuperar una lista de datos sumarizada o agrupada
2. mostrarla al usuario

Finalmente, consideramos dos arquitecturas físicas reconocidas. El caso co-localizado - al que consideramos mas importante, al menos para aplicaciones web típicas - tiene una capa de presentación actuando como cliente local de la capa de negocio. El caso remoto tiene un cliente remoto (por ejemplo, un motor de servlets o un cliente swing ejecutándose en diferentes capas físicas) accediendo a la capa de negocio.

Primero necesitamos datos. La interacción con datos relacionales es fundamental en casi cualquier aplicación web o empresarial. Las aplicaciones que implementan lógica de negocio no trivial se benefician de una representación orientada a objetos de los datos (un modelo de dominio) que tiene todos los beneficios de las técnicas de orientación a objetos como herencia y polimorfismo. Por varias razones (No quiero repetir los argumentos que he expresado largamente en Hibernate in Action), aplicaciones que usan modelos de dominio totalmente orientados a objetos necesitan una solución automatizada al problema de adaptación OR. EJB3 incorpora una especificación ORM muy sofisticada que se basa fuertemente en la experiencia en CMP 2.1, Hibernate y TopLink de Oracle.

Nuestra aplicación de subastas tiene Users (Usuarios), Items (Items) y Bids (Ofertas). Vamos a implementarlos como entity beans al estilo 3.0. Comenzaremos con Item:

@Entity

public class Item {
private Long id;
private User seller;
private Collection<Bid> bids = new ArrayList<Bid>();
private String description;
private String shortDescription;
private Date auctionEnd;
private int version;

public Item(User seller, String desc, String shortDesc, Date end) {
this.seller = seller;
this.description = desc;
this.shortDescription = shortDesc;
this.auctionEnd = end;
}

protected Item() {}

@Id(generate=AUTO)
public Long getId() {
return id;
}
protected void setId(Long id) {
this.id = id;
}

@Column(length=500)
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}

public Date getAuctionEnd() {
return auctionEnd;
}
protected void setAuctionEnd(Date end) {
this.auctionEnd = end;
}

@Column(length=100)
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}

@JoinColumn(nullable=false, updatable=false)
public User getSeller() {
return seller;
}
protected void setSeller(User seller) {
this.seller = seller;
}

@OneToMany(cascade=ALL)
protected Collection<Bid> getBids() {
return bids;
}
protected void setBids(Collection<Bid> bids) {
this.bids = bids;
}

@Version
public int getVersion() {
return version;
}
protected void setVersion(int version) {
this.version = version;
}

public Bid bid(BigDecimal amount, User bidder) {
Bid newBid = new Bid(this, amount, bidder);
bids.add(newBid);
return bid;
}
}
Lo mas llamativo, inicialmente, son las anotaciones. No se necesita ningún deployment descriptor! EJB 3.0 permite elegir entre el uso de anotaciones y descriptores de despliegue en XML, pero lo esperado es que las anotaciones serán el caso mas común.

La anotación @Entity le dice al contenedor que esta clase es un CPM entity bean. Las anotaciones opcionales @Column definen los mappings de las columnas para las propiedades persistentes (si estuviéramos con los mappings de datos antiguos, podríamos definir los nombres de las columnas en estas anotaciones). Las anotaciones @Id seleccionan la propiedad de clave primaria del entity bean. En este caso, el entity bean tiene una clave primaria generada. Las anotaciones opcionales @OneToMany definen asociaciones de uno a muchos, y especifican un estilo de cascada de ALL. La anotación opcional @JoinColumn define un mapping de columna para una asociación. En este caso, obliga a la asociación a ser obligatoria e inmutable. La anotación @Version define la propiedad utilizada por el contenedor para su bloqueo optimista.

Quizás lo mas importante a tener en cuenta aquí es que casi todas las anotaciones son opcionales. EJB3 utiliza una configuración por excepciones, es decir que la especificación define valores por defecto útiles y lógicos y así no es necesario especificar anotaciones para el caso normal. Por ejemplo, no hemos incluido ninguna anotación en una de las propiedades persistentes.

También llamativo es que, además de las anotaciones, Item no depende de clases o interfaces en javax.ejb. Esto fue una meta prioritaria de EJB3, y ayuda significativamente a reducir el "ruido".

Los entity beans no necesitan una interface local o remota, lo que también reduce el ruido.

Es momento de implementar Bid:

@Entity

public class Bid {
private Long id;
private Item item;
private BigDecimal amount;
private Date datetime;
private User bidder;
private boolean approved;

protected Bid() {}

public Bid(Item item, BigDecimal amount, User bidder) {
this.item = item;
this.amount = amount;
this.bidder = bidder;
this.datetime = new Date();
}

@Id(generate=AUTO)
public Long getId() {
return id;
}
protected void setId(Long id) {
this.id = id;
}

@Column(nullable=false)
public BigDecimal getAmount() {
return amount;
}
protected void setAmount(BigDecimal amount) {
this.amount = amount;
}

@Column(nullable=false)
public Date getDatetime() {
return datetime;
}
protected void setAmount(Date datetime) {
this.datetime = datetime;
}

@JoinColumn(nullable=false)
public User getBidder() {
return bidder;
}
protected void setBidder(User bidder) {
this.bidder = bidder;
}

@JoinColumn(nullable=false)
public Item getItem() {
return item;
}
protected void setItem(Item item) {
this.item = item;
}

public boolean isApproved() {
return approved;
}
public void setApproved(boolean approved) {
this.approved = approved;
}


}
Les dejaré a ustedes el escribir el entity bean User!

Observen que ahora nuestras clases del modelo de dominio son simples JavaBeans. Esto significa que son testeables fuera del contenedor EJB.

Y que sobre las home interfaces? Bueno, la necesidad de home interfaces ha sido eliminada por EJB3, asi que no las necesitaremos.

Ahora, miremos nuestro primer caso de uso, actualizando un Item. Asumiremos una arquitectura co-localizada, e implementaremos nuestra lógica de negocio en un stateless session bean con una interface de negocio local. Una interface de negocio define operaciones visibles al cliente (vuestros session beans pueden tener tantas interfaces de negocio como quieran). Definiremos la interface local Auction (Subasta) asi:

public interface Auction {

public Item getItemById(Long itemId);
public void updateItem(Item item);
}
Las interfaces de negocio son locales por defecto, por lo tanto no es necesaria ninguna anotación @Local.

Dado que nuestro entity bean Item es también un JavaBean, podemos pasarlo directamente a una JSP (por ejemplo). No necesitamos ningún DTO, ni métodos con declaraciones complejas.

La clase bean AuctionImpl implementa esta interface:

@Stateless

public class AuctionImpl implements Auction {
@Inject public EntityManager em;

public Item getItemById(Long itemId) {
return em.find(Item.class, itemId);
}

public void updateItem(Item item) {
em.merge(item);
}
}
Wow. Eso fue muy fácil!

La anotación @Inject se utiliza para indicar que un campo de un session bean es actualizado por el contenedor. En EJB3.0, los session beans no necesitan utilizar JNDI para obtener referencias a recursos u otros EJBs. En este caso, el contenedor inyecta una referencia al EntityManager, la interface para operaciones relacionadas con la persistencia en entity beans.

Observen que el contenedor se encarga del bloqueo optimista en forma transparente.

Supongamos que nuestro Client es un servlet. El código para mostrar un Item sería algo como esto:

Long itemId = new Long( request.getParameter("itemId") );


Auction auction = (Auction) new InitialContext().lookup("Auction");

Item item = auction.getItemById(itemId);

session.setAttribute("item", item);
Dado que el entity bean es solo un simple JavaBean, la JSP puede utilizarlo directamente. El segundo request, que actualiza el Item, podría ser algo asi:

Item item = (Item) session.getAttribute("item");


item.setDescription( request.getParameter("description") );
item.setShortDescription( request.getParameter("shortDescription") );

Auction auction = (Auction) new InitialContext().lookup("Auction");

auction.updateItem(item);
Tengan en cuenta que otros grupos de expertos están explorando alternativas a fin de eliminar búsquedas JNDI, como por ejemplo estas búsquedas del session bean.

Vamos ahora al segundo caso de uso, crear un nuevo Bid. Agregaremos un nuevo método al Auction:

public interface Auction {

public Item getItemById(Long itemId);
public void updateItem(Item item);
public Bid bidForItem(Item item, BigDecimal amount, User bidder)
throws InvalidBidException;
public void approveBid(Long bidId);
}
Debemos, por supuesto, implementar estos métodos nuevos en AuctionImpl:

@Stateless

public class AuctionImpl implements Auction {
@Inject public EntityManager em;
@Inject QueueConnectionFactory bidQueue;

...

public void approveBid(Long bidId) {
Bid bid = em.find(Bid.class, bidId);
bid.setApproved(approved);
}

public Bid bidForItem(Item item, BigDecimal amount, User bidder)
throws InvalidBidException {

String query = "select max(bid.amount) from Bid bid where "
+ "bid.item.id = :itemId and bid.approved = true";

BigDecimal maxApprovedBid =
(BigDecimal) em.createNamedQuery(query)
.setParameter( "itemId", item.getId() )
.getUniqueResult();

if ( amount.lessThan(maxApprovedBid) ) {
throw new InvalidBidException();
}

Bid bid = item.createBid(amount, bidder);

em.create(bid);

requestApproval(bid, bidQueue);

return bid;

}

private static void requestApproval(Bid bid, Queue queue) {
...
}
}
El método bidForItem() ejecuta una query EJB QL que determina la oferta (Bid) actual máxima aprobada. Si la cantidad de la nueva oferta es mayor, instanciamos una nueva Bid y la hacemos persistente llamando a EntityManager.create().

El método requestApproval() envía un mensaje a la cola, pidiendo al backend que verifique la capacidad de pago del ofertante. El session bean obtiene una referencia a una JMS QueueConnectionFactory por inyección de dependencias.

Necesitamos un message driven bean, BidApprovalListener, para recibir la respuesta del backend. Cuando la respuesta es recibida, le enviaremos un correo al ofertante, y marcaremos la oferta como aprobada.

@MessageDriven 

public class BidApprovalListener implements MessageListener {
@Inject javax.mail.Session session;
@Inject Auction auction;

public void onMessage(Message msg) {

MapMessage mapMessage = (MapMessage) msg;
boolean approved = mapMessage.getBoolean("approved");
long bidId = mapMessage.getLong("bidId");

auction.approveBid(bidId);

notifyApproval(bid, session);

}

private static void notifyApproval(Bid bid, Session session) {
...
}
}
Observen que el message driven bean obtiene su sesión JavaMail y una referencia al bean de session Auction por inyección de dependencias.

Nuestro último caso de uso es bien fácil. Agregamos un método mas a Auction:

public interface Auction {

public Item getItemById(Long itemId);
public void updateItem(Item item);
public Bid bidForItem(Item item, BigDecimal amount, User bidder)
throws InvalidBidException;
public void approveBid(Long bidId);
public List<ItemSummary> getItemSummaries();
}
Y lo implementamos en AuctionImpl:

@Stateless

public class AuctionImpl implements Auction {
...

public List<ItemSummary> getItemSummaries() {
String query = "select new "
+ "ItemSummary(item.shortDescription, item.id, max(bid.amount)) "
+ "from Item item left outer join item.bids bid";

return (List<ItemSummary>) em.createQuery(query)
.listResults();
}
}
ItemSummary es solo un simple JavaBean con un constructor apropiado, por lo que no necesitamos mostrarlo aquí.

La query EJB QL demuestra cuán fácil es hacer outer joins, proyecciones y agregaciones en EJB3. Estas (y otras) nuevas características hacen casi obsoleto el antipatrón Fast Lane Reader.

Bueno, llegamos al final de la primera parte. Hemos visto las siguientes simplificaciones:

  • Las anotaciones reemplazan a los complejos descriptores de despliege en XML

  • Eliminación de las home interfaces

  • Eliminación de dependencias a clases e interfaces en javax.ejb

  • Los entity beans son ahora simples JavaBeans, sin interfaces locales o remotas

  • La inyección de dependencia reemplaza a las búsquedas JNDI

  • Eliminación de los DTOs

  • Eliminación del anti patrón Fast Lane Reader

La próxima vez, consideraremos los clientes remotos...

viernes, julio 02, 2004

Java Practices

Un sitio muy útil sobre prácticas de desarrollo en java: Java Practices

Servlet Performance Report

En el nuevo test de rendimiento de servlets de Web Performance (Servlet Performance Report) se pueden comparar las últimas versiones de Tomcat, Orion, Resin, Websphere, Jetty y JRun.

Dentro del reporte se pueden ver los tiempos de carga de páginas ante un incremento en la cantidad de usuarios y la cantidad de errores (connection refused o unexpected socket closure) que se generan. Ambos gráficos son fusionados luego en cantidad de hits por segundo. También se puede ver la carga de CPU para cada servidor.

Cabe destacar que todos los servidores fueron comparados con su configuración original, sin ninguna clase de "tunning". Aún asi es asombroso ver a Websphere muy por detrás del resto.

jueves, julio 01, 2004

Outsource



Encontrado aqui.

JSR 220: Enterprise JavaBeans 3.0

Está disponible un Early Draft for Review de la especificación de EJB 3.0.

Entre muchas otros cambios, la que impresiona mas es la que da forma a las metadata annotations, tal que para especificar un stateful bean, solo haría falta:

@Stateful public class CartBean implements ShoppingCart {
  private float total;
  private Vector productCodes;
  public int someShoppingMethod(){...};
  ...
  ejbRemove() {...};
}