Llamada por RequestFactory
¿Qué es RequestFactory?
RequestFactory es una alternativa a RPC, es una tecnología orientada a interfaces partiendo de la premisa que los datos a compartir entre cliente y servidor no son equivalentes, a diferencia de GWT-RPC, que es una tecnología orientada al tipo de dato en concreto.
¿Cómo se configura?
Habilitar
el uso de RequestFactory, es tan simple como configurar en nuestro web.xml un servlet que atienda las peticiones entrantes. RequestFactory
trabaja con un servlet propio denominado RequestFactoryServlet,
solo debemos agregar unas líneas sobre el archivo WEB-INF/web.xml,
como se haría con cualquier aplicación Java.
<web-app>
. . .
<servlet>
<servlet-name>gwtRequest</servlet-name>
<servlet-class>
com.google.gwt.requestfactory.server.RequestFactoryServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>gwtRequest</servlet-name>
<url-pattern>/gwtRequest</url-pattern>
</servlet-mapping>
.
. .
</web-app>
<inherits name='com.google.web.bindery.requestfactory.RequestFactory'
/>
Arquitectura de RequestFactory
Entidades:
Una Entidad es una clase de dominio, y tienen el concepto de entidad persistente, estas pueden ser persistidas en un almacén de datos como una base de datos relacional o el almacén de datos de AppEngine o cualquier almacén de datos en particular. Podemos hacer uso de algún framework de persistencia como JPA o JDP, pero RequestFactory no obliga a la utilización de ninguno, a continuación representamos una Entidad haciendo uso de JPA la cual representa a un usuario:
@Entity
@Table
( name = "User" )
public class
User implements
Serializable {
@Id
@GeneratedValue
( strategy = GenerationType.IDENTITY
)
@Column
( name = "id" )
private
Long id;
@Basic ( optional
= false )
@NotNull
@Size ( min = 1,
max = 100 )
@Column
( name = "username" )
private
String username;
. . .
. . .
@Version
@Column
( name = "version" )
private
Long version;
public
User() {
}
public
Long getId() {
return id;
}
. . .
. . .
public void
setUsername( String username ) {
this.username = username;
}
. . .
. . .
}
Entity Proxy:
@ProxyFor(value = User.class, locator = UserLocator.class)
public interface
UserProxy extends EntityProxy {
@Override
EntityProxyId<UserProxy> stableId();
String getUsername();
void setUsername(String name);
}
Value Proxy
Un ValueProxy no requiere entidad (id) o la declaración de la versión, este puede representar cualquier tipo, lo podemos utilizar para representar objetos enbebidos de una entidad o para transmitir por ejemplo alguna clase que representae el criterio en alguna búsqueda. Para ejemplificarlo, podríamos tener un proxy que represente la constraint del usuario (username y password) a transferir al servidor, en un supuesto login.
@ProxyFor(UserConstraint.class)
public interface
UserConstraint extends
ValueProxy{
String getUserName();
void
setUserName(String username);
String getPassword();
void
setPassword(String password);
}
}
UserConstraint es un Java Bean en el lado del servidor, no una entidad.
public class UserConstraint implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
}
podemos notar la ausencia de las propiedades id y version.
Implementando el DAO:
@Service(value = UserDao.class, locator = CustomServiceLocator.class)public interface UserRequest extends RequestContext {
Request<Void> remove(UserProxy employee);
Request<List<UserProxy>> findAll();
Request<String> login(String username, String password);
Request<UserProxy> getUserLogued(String token);
}
public interface CustomRequestFactory extends RequestFactory {
UserRequest userRequest();
}
public interface ClientFactory {
EventBus getEventBus();
MainLayout getLayout();
ClientResource getResource();
CustomRequestFactory requestFactory();
}
Services:
El service, se ubica en lo que se denomina Service Layer en una aplicación clásica, es un objeto cuyo ciclo de vida es manejado por el servidor. Por simplicidad en el post, utilizaremos DAOs ( Data Access Objects) para representar la capa de servicios, lo más común es utilizar otra capa superior a los DAO que contenga la lógica de negocios necesaria y ésta capa acceda a los DAOs.
En nuestro caso los DAOs serán los encargados de interactuar con las Entidades, siguiendo con el ejemplo, un DAO que interactúe con nuestra entidad User:
public interface UserDao extends Dao<User, Long>{
User findByUserNamePassword(String username, String passwd);
String login(String username, String password);
User getUserLogued(String token) throws TokenException;
}
public interface Dao<E, T> {
void persist(E entity);
void remove(E entity);
E findById(T id);
List<E> findAll();
}
public class UserJpaDao implements UserDao {
private Provider<EntityManager> em;
@Inject
public UserJpaDao(Provider<EntityManager> em) {
this.em = em;
}
@Override
public void persist(User entity) {
em.get().getTransaction().begin();
this.em.get().persist(entity);
em.get().getTransaction().commit();
}
@Override
public void remove(User entity) {}
@Override
public User findById(Long id) {
return this.em.get().find(User.class, id);
}
@Override
public List<User> findAll() {
return em.get().createNamedQuery("User.findAll").getResultList();
}
. . .
. . .
}
RequestFactory
Es el componente principal de la arquitectura, que le da nombre al framework.
Ocupa el lugar del cliente en la capa Remote Facade clásica. RequestFactory es una
interface que debe ser extendida para instanciar diferentes RequestContext. Un
RequestContext es un “ámbito” en el que viven los Proxies devueltos por el servidor:
Request<Void> persist(UserProxy user);
Request<Void> remove(UserProxy employee);
Request<List<UserProxy>> findAll();
Request<String> login(String username, String password);
Request<UserProxy> getUserLogued(String token);
}
Aquí vemos ejemplificado un RequestContext que se mapea con nuestra interface de servicio, en el caso particular UserDao. Este mapeo es resuelto por una clase Locator, los métodos definidos en el Request se deben corresponder con los métodos definidos en la interface de servicios.
UserRequest userRequest();
}
Cada Request es instanciado por RequestFactory, por ende debemos crear una interface como la que se muestra anteriormente.
El nombre del método instanciador es irrelevante, pero por conveniencia y prolijidad en el código es recomendable que se llame igual que el request.
A continuación veremos como instanciamos nuestro RequestFactory en el cliente:
EventBus getEventBus();
MainLayout getLayout();
ClientResource getResource();
CustomRequestFactory requestFactory();
}
public class ClientFactoryImpl implements ClientFactory{
private EventBus bus = null;
private MainLayout layout;
private ClientResource resource = null;
private CustomRequestFactory requestFactory;
@Override
public EventBus getEventBus() {
if ( bus == null )
bus = new SimpleEventBus();
return bus;
}
. . .
. . .
@Override
public CustomRequestFactory requestFactory() {
if ( requestFactory == null ){
requestFactory = GWT.create(CustomRequestFactory.class);
requestFactory.initialize(this.getEventBus());
}
return requestFactory;
}
}
ServiceLocators:
Estos son utilizados solo por el servidor, precisamente por el RequestFactoryServlet para encontrar las instancias de nuestros servicios, en nuestro caso DAOs, mostraremos una simple implementación, en la práctica se suele utilizar inyección de dependencias para realizar esto, por ejemplo Guice.
public class DaoServiceLocator implements ServiceLocator {
if(clazz.isAssignableFrom(UserDao.class)) {
return UserDaoProvider.get();
}
return null;
}
Donde UserDaoProvider.get() es una clase que nos retorna una instancia de nuestro DAO, el mismo suele ser Singleton. Los DAOs también suelen ser manajados mediante inyección de dependencias (ID), lo cual delega en el framework de ID la creación y ciclo de vida de los objetos.
EntityLocator
Además de los servicios, debemos ser capaces de encontrar nuestras entidades, eso lo haremos haciendo uso de Locators. Recuerden el artibuto locator en la anotación @ProxyFor en el proxy UserProxy. El locator tiene la responsabilidad de instancia las entidades y de extraer su ID y versión.
private final UserDao dao;
@Inject
UserLocator(UserDao dao) {
this.dao = dao;
}
UserLocator(UserDao dao) {
this.dao = dao;
}
@Override
public User create(Class<? extends User> clazz) {
return new User();
}
@Override
public User find(Class<? extends User> clazz, Long id) {
return dao.findById(id);
}
@Override
public Class<User> getDomainType() {
return User.class;
}
@Override
public Long getId(User user) {
return user.getId();
}
@Override
public Class<Long> getIdType() {
return Long.class;
}
@Override
public Object getVersion(User user) {
return user.getVersion();
}
}
Poniendo las todo a trabajar
Ahora veremos como realizamos una llamada desde el cliente haciendo uso de RequestFactory:
final UserRequest request = rf.userRequest();
request.login(userTextBox.getText(), passwdTextBox.getValue())
.fire(new Receiver<String>() { // 1er Llamada
@Override
public void onSuccess(String response) {
.fire(new Receiver<String>() { // 1er Llamada
@Override
public void onSuccess(String response) {
Window.alert(response);
}
}
@Override
public void onFailure(ServerFailure error) {
Window.alert(error.getMessage());
}
});
factory, es la implementación de nuestro ClientFactory, en primera instancia obtenemos un RequestContext, para luego hacer la llamada al método login, enviando como parámetros el nombre de usuario y contraseña. El método login, retorna una instancia de la clase Request, de la cual utilizamos el método fire, que es el que efectivamente realiza la llamada. Antes de llamar a fire, podemos configurar el Request con más parámetros, por ejemplo: with(..), al cual le podemos pasar un conjunto de strings con los nombres de las propiedades "enlazadas" que queremos que traiga el Objeto. Esto se utiliza, cuando un Proxy tiene dentro de sus propiedades otros proxys, por defecto, si no se configura el Request para traerlos, estos no se traen, con lo cual se corta el árbol de dependencias, lo que es muy eficiente en cuanto a transferencias de información.
El método fire, tiene como parámetro una instancia de Reseiver<T> donde T es el tipo de dato retornado por el servicio.
Este Receiver nos obliga a la implementación de los métodos onSeccess y onFailure donde manejaremos el retorno del servicio.
Conclusión
Con esta guía de implementación podrán realizar llamadas con RequestFactory (RF) intercambiado datos. Una de las cosas importantes que resuelve RF, es el mapeo de Entidad a DTO y viceversa, en el caso de RF éstos DTO son llamados Proxy y su implementación es generada por RF.
Comentarios