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...

4 comentarios:

Anónimo dijo...

http://www.pcmasmas.com.ar/viewtopic.php?p=79698#79698

scaamaño dijo...

Muy buena traducion a un excelente artículo. Gracias

Iván dijo...

Gracias por el aporte. Es significativo realmente.
Tengo una pregunta... ¿@entity solo sirve para especificar un CMP?, ¿como especifico un BMP con EJB3?. Buscare en la red, si encuentro la respuesta la hago saber.

Mariano dijo...

falta la segunda parte jajajajaja...Me dejas con las ganas, igual gracias por esta traduccion, estoy tratando de insertarme en EJB3 y me sirve muchisimo para tener una primera idea, soy de Mar del Plata, argentina les dejo mi correo por si quieren agregarme flashpoint96@hotmail.com

Saludos y gracias de nuevo