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>

Además debemos configurar RequestFactory en el módulo de nuestra aplicación:


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

  Un entity proxy es la representación en el cliente de una entidad de dominio definida en el servidor. Estos Proxy son el equivalente a los DTO (Data Transfer Object) cuando utilizamos GWT-RPC. Estos son interfaces sin implementación que materializan mediante getters y setters las propiedades de la entidad de dominio a transferir. Estas interfaces extienden EntityProxy y utilizan la anotación @ProxyFor para detonar a que entidad están haciendo referencia.

@ProxyFor(value = User.class, locator = UserLocator.class)
public interface UserProxy extends EntityProxy {

      @Override
      EntityProxyId<UserProxy> stableId();

      String getUsername();

      void setUsername(String name);
     
}

  Además un proxy en particular puede manipular mediante getter y setter otro proxy o una lista de éstos, RequestFactory se encargará automáticamente de convertir esto es sus representaciones del lado del servidor.

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.

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();
}

Implementando el DAO:


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:

@Service(value = UserDao.class, locator = CustomServiceLocator.class)public interface UserRequest extends RequestContext {
   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.

public interface CustomRequestFactory extends RequestFactory {
   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:

public interface ClientFactory {

    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 {

@Overridepublic Object getInstance(Class<?> clazz) {

    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.

@Singletonpublic class UserLocator extends Locator<User, Long> {
   private final UserDao dao;
   
   @Inject
   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 CustomRequestFactory rf = factory.requestFactory();
final UserRequest request = rf.userRequest();

request.login(userTextBox.getText(), passwdTextBox.getValue())
   .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.

Referencias

Comentarios

Entradas más populares de este blog

¿Qué es GWT?