Modelo «push» contra modelo «pull» en frameworks web

Escrito por el , actualizado el .
java programacion tapestry planeta-codigo
Enlace permanente Comentarios

Apache Tapestry

En la mayoría de frameworks de desarrollo de aplicaciones o páginas web para producir el contenido HTML que se envía al cliente se emplea un modelo en el que el controlador proporciona los datos que combinados con una plantilla producen el HTML. Este modelo también es el empleado habitualmente en muchos motores de plantillas (thymeleaf, mustache, …). Sin embargo, hay dos modelos que se pueden seguir para producir un texto como resultado dada una plantilla y datos:

  • Push: este es el modelo comentado. El controlador recupera de antemano todos los datos que necesita la vista, el controlador también determina la vista o plantilla que se usar. Combinando los datos y la plantilla se produce el resultado.

Modelo push

Los pasos que se siguen en este modelo son:

  1. La petición llega al servidor
  2. El dispatcher redirige la petición al controlador
  3. El controlador solicita los datos a la base de datos
  4. El controlador obtiene los datos de la base de datos
  5. El controlador redirige a la vista y le envía los datos que necesita
  6. La vista genera el contenido y se envía al cliente
  • Pull: en este modelo el controlador no conoce los datos que usará la vista y es esta la que los solicita según necesita. La vista tira del controlador, el controlador solo debe ofrecer el soporte par que la vista pueda recuperar los datos que necesite.

Modelo pull

Los pasos que se siguen en este modelo varían ligeramente del modelo push pero de forma importante, son:

  1. La petición llega al servidor
  2. El dispatcher redirige la petición al controlador
  3. El controlador redirige a la vista
  4. La vista pide los datos que necesita al controlador y el controlador los pide a la base de datos
  5. La vista obtiene los datos que ha pedido del controlador
  6. La vista genera el contenido y se envía al cliente

El modelo push es empleado en muchos de los frameworks web más usados, algunos ejemplos son Symfony, Django, Grails o ASP.NET MVC. En la categoría de frameworks que usan un modelo pull está Apache Tapestry.

Al modelo push le encuentro algunos problemas. Un problema es que el controlador debe conocer que datos necesita la vista y si la vista tiene cierta lógica esta la tendremos duplicada tanto en en controlador como en la vista. Supongamos que en una aplicación tenemos un usuario y dirección con una relación de 1 a 1 entre ambos y que debemos mostrar en una página el usuario y su dirección solo si solo si es un usuario VIP. En el controlador tendremos que recuperar el usuario, comprobar si es VIP y si lo es recuperar su dirección. El problema está que en la vista deberemos hacer también una comprobación si el cliente es VIP o al menos si a la vista se le ha proporcionado una dirección, como resultado la comprobación la tendremos duplicada tanto en el controlador como en la vista, como sabemos la duplicación de código y lógica habitualmente no es buena idea ya que a la larga dificulta el mantenimiento de la aplicación.

En Grails (pero podría ser cualquier otro framework o motor de plantillas push) podríamos visualizar el usuario y su dirección si es VIP de la siguiente forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Grails
// Controlador (groovy)
def showUsuario() {
    def usuario = Usuario.get(params.long('id'))
    def direccion = null
    if (usuario.isVIP()) {
        direccion = usuario.direccion
    }
    render(view:'show', model: [usuario:usuario, direccion:direccion])
}

// Vista (gsp)
Nombre: ${usuario.nombre}
<g:if test="${usuario.vip}">
    Dirección: ${direccion.toString()}
</g:if>
push.txt

Si usamos hibernate la recuperación de la dirección podemos hacerla navegando la relación pero he querido recuperarla en el controlador expresamente para el ejemplo, si no pudiésemos usar hibernate para recuperar el dato relacionado probablemente lo que haríamos es recuperar el dato en el controlador como en el ejemplo.

Otro problema del modelo push es que si la vista es usada en múltiples controladores, y precisamente la separación entre vistas y controladores uno de sus motivos es para esto, todos estos controladores van a compartir el código para recuperar los datos que necesite la vista, dependiendo del número de datos y de veces que empleemos una vista en múltiples controladores quizá debamos hacer una clase asociada a la vista que recupere los datos para evitar tener código duplicado (y exactamente esto es lo que se hace en Tapestry).

En el modelo pull el controlador no debe conocer que datos necesita la vista y si hay lógica para mostrar ciertos datos está lógica solo la tendremos en la vista. Aunque el controlador no deba conocer que datos en concreto necesite la vista si debe ofrecer el soporte para que la vista los recupere cuando necesite. Como se puede ver el código en el siguiente ejemplo la comprobación de si el usuario es VIP solo está en la vista. En Tapestry cada vista tiene asociado una clase Java que es la encargada de ofrecer el soporte para que la vista pueda recuperar los datos, el conjunto de controlador más vista es lo que en Tapestry se conoce como componente, si el componente se usa varias veces en el mismo proyecto no necesitamos duplicar código.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Tapestry
// Controlador (java)
public Usuario getUsuario() {
    return usuarioDAO.get(id);
}

public Direccion getDirecion() {
    return getUsuario().getDireccion();
}

// Vista (tml)
Nombre: ${usuario.nombre}
<t:if test="usuario.vip">
    Direccion: ${direccion.toString()}
<t:if>
pull.txt

¿Podemos emplear un modelo pull en un framework que normalmente se suele usar un modelo push? Sí, basta que en el modelo de la vista pasemos un objeto que le permita recuperar los datos que necesite. En Grails empleando un modelo pull el código podría quedarnos de la siguiente forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Grails
// Controlador (groovy)
def showUsuario() {
    render(view:'show', model: [view:new View(params)])
}

private class View {

    Map params

    View(Map params) {
        this.params = params
    }

    def getUsuario() {
        return Usuario.get(params.long('id'))
    }

    def getDireccion() {
        return usuario.direccion
    }
}

// Vista (gsp)
Nombre: ${view.usuario.nombre}
<g:if test="${view.usuario.vip}">
    Dirección: ${view.direccion.toString()}
</g:if>
pull-in-push.txt

Como se ve el if de comprobación en el controlador desaparece, a pesar de todo si la vista fuese usada por varios controladores deberíamos crear algo para evitar tener duplicado el código que permite recuperar los datos a la vista. Aunque esto es perfectamente posible no es la forma habitual de usar los modelos push.

Este ejemplo es muy sencillo y empleando cualquiera de los dos modelos es viable, pero cuando el número de datos a recuperar en las vistas y el número de veces que se reutiliza una vista aumenta (y en teoría la separación entro controlador y vista uno de sus motivos es posiblemente para reutilizarlas) el modelo push presenta los problemas que he comentado que el modelo pull no tiene.

Portada libro: PlugIn Tapestry

Libro PlugIn Tapestry

Si te interesa Apache Tapestry descarga gratis el libro de más de 300 páginas que he escrito sobre este framework en el formato que prefieras, PlugIn Tapestry: Desarrollo de aplicaciones y páginas web con Apache Tapestry, y el código de ejemplo asociado. En el libro comento detalladamente muchos aspectos que son necesarios en una aplicación web como persistencia, pruebas unitarias y de integración, inicio rápido, seguridad, formularios, internacionalización (i18n) y localización (l10n), AJAX, ... y como abordarlos usando Apache Tapestry.



Comparte el artículo: