Play Framework

Vamos a usar el framework Play2 para implementar el back-end, es decir el lado del servidor web (server-side) para nuestras aplicaciones cuya parte cliente (client-side o front-end) construimos con AngularJS.

Intro

Play es un framework relativamente nuevo, que está bastante de moda actualmente (estamos en abril 2014 :P). Especialmente en el ambiente de desarrollo Scala. Igualmente no se asusten, play viene en dos "sabores", para quienes quieran trabajar en Scala o bien en Java tradicional.
El core en sí de play está hecho en Scala, pero proveen soporte para que trabajemos en Java.

Igualmente van a ver que la gran mayoría de las personas lo usa con Scala, porque es con lo que más provecho se le puede sacar.

En princpio Play sirve para construir aplicaciones web en forma completa. Es decir el lado servidor como también el lado cliente.
Sin embargo, podemos usarlo tan solo como un servicio web (web service).
Ya vamos a explicar todo esto.

Instalación


Ejemplo HolaMundo en Play

Antes que nada, en el sitio de play ya existe una buena documentación sobre como crear un proyecto, etc (http://www.playframework.com/documentation/2.2.x/JavaTodoList)

Creamos un proyecto con

play new libros-backend-play

Ahí mismo play los va a ir guiando con un wizard (de consola):

$ play2 new libros-backend-play

       _            _

 _ __ | | __ _ _  _| |

| '_ \| |/ _' | || |_|

|  __/|_|\____|\__ (_)

|_|            |__/


play! 2.1.5 (using Java 1.7.0_06 and Scala 2.10.0), http://www.playframework.org

The new application will be created in /opt/dev/data/repo/ui/web/angular/libros-backend-play

What is the application name? [libros-backend-play]

> libros-backend-play

Which template do you want to use for this new application? 

  1             - Create a simple Scala application

  2             - Create a simple Java application

> 2

OK, application libros-backend-play is created.

Have fun!


Esto va a crear un proyecto con la siguiente estructura:
libros-backend-play
    ├── app
    │   ├── controllers
    │   │   └── Application.java
    │   └── views
    │       ├── index.scala.html
    │       └── main.scala.html
    ├── conf
    │   ├── application.conf
    │   └── routes
    ├── project
    │   ├── build.properties
    │   ├── Build.scala
    │   └── plugins.sbt
    ├── public
    │   ├── images
    │   │   └── favicon.png
    │   ├── javascripts
    │   │   └── jquery-1.9.0.min.js
    │   └── stylesheets
    │       └── main.css
    ├── README
    └── test
        ├── ApplicationTest.java
        └── IntegrationTest.java

Lo importante que podemos nombrar acá es:
  • En conf:
    • application.conf: configuraciones generales de la aplicación, por ejemplo nivel de LOG, conexión con la persistencia, etc
    • routes: este archivo es importante, acá es donde vamos a configurar de cierta forma el "contrato" de nuestra aplicacion, a través de definir las URL's que sabe atender, en qué forma (GET, POST, etc) y algunos parámetros que espera.
  • app/controllers:
    • Los controllers son los objetos encargados de recibir el pedido del cliente, interpretar los parámetros, hacer algo, y luego retornar una respuesta.
  • app/views:
    • Cuando construimos una aplicación web "completa" con Play, es el mismo servidor el que ante un pedido retorna ya el html que el browser simplemente debe mostrar. Para eso, en general el html se "genera" porque tiene que incluir información dinámica (por ejemplo un listado de libros). Esa generación se hace en base a "templates", que son estas vistas en play. Nosotros no vamos a utiliar vistas, porque nuestra vista va a estar en el lado del cliente con Angular.
  • public
    • Contiene todo el contenido estático de la aplicación web, por ejemplo hojas de estilo (CSS), librerías javascript, etc. De nuevo, nuestro servidor más que una aplicación web va a ser un "servicio web", bien desacoplado del html y de todas estas cosas, con lo cual no vamos a usar esta parte.

Importando en eclipse

Necesitamos importar este nuevo proyecto en eclipse. Para eso, tenemos que convertirlo en proyecto eclipse a través de un comando de play

play eclipse

[info] Loading project definition from /opt/dev/data/repo/ui/web/angular/libros-backend-play/project
[info] Set current project to libros-backend-play (in build file:/opt/dev/data/repo/ui/web/angular/libros-backend-play/)
[info] About to create Eclipse project files for your project(s).
[info] Updating {file:/opt/dev/data/repo/ui/web/angular/libros-backend-play/}libros-backend-play...
[info] Done updating.                                                                  
[info] Compiling 4 Scala sources and 2 Java sources to /opt/dev/data/repo/ui/web/angular/libros-backend-play/target/scala-2.10/classes...
[info] Successfully created Eclipse project files for project(s):
[info] libros-backend-play

Luego desde el eclipse, File->Import, "Existing project into workspace". Buscamos la carpeta y aceptar.

Ejecutar la aplicación

Para levantar el servidor, desde la consola play ejecutamos "run"


[libros-backend-play] $ run

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

Como vemos ahí, el servidor está escuchando en el puerto "9000". Así que desde un browser podemos pedir la página:

http://localhost:9000/

Ahí van a ver una página de Play que sirve de tutorial.

Agregar dependencia a un proyecto maven (ej: un dominio)

Existe una forma de configurar Play para que lea artefactos del repo local de Maven. Y así poder agregar una dependencia a uno de nuestros proyectos. Por ejempo el dominio de una aplicación.

Para eso deben modificar el archivo project/Build.scala y agregar la linea en negrita:

  val main = play.Project(appName, appVersion, appDependencies).settings(
      // Add your own project settings here
      resolvers += ("Local Maven" at "file://" Path.userHome.absolutePath "/.m2/repository")
  )

Más info acá

Luego, podemos agregar la dependencia, en el mismo archivo en la sección appDependencies:

val appDependencies = Seq(
    // Add your project dependencies here,
    javaCore,
    javaJdbc,
    javaEbean,
    "org.uqbar.edu" % "libros-domain-java" % "1.0.0-SNAPSHOT"
  )

De nuevo acá se muestra la linea en negrita.
El formato es:

"groupId" % "artifactId" % "version"

Atenti además a que estamos tocando código scala. Y esto es una lista de elementos. Asi que para agregar uno, hay que estar atento a las comas entre elemento y elemento.

Igualmente, ojo, porque el proyecto play va a pasar a depender del JAR generado por maven de sus proyectos.
Con lo cual, cada vez que hagan cambios en el dominio, habrá que generar un nuevo jar y refrescar la dependencia en la app play.

1) en dominio:    
  mvn clean install

2) Borrar el directorio cache de play, por ejemplo:
  rm -rf /opt/dev/soft/tools/play/repository/cache/org.uqbar.edu/

La primer parte subrayada es el path a donde tengo instalado play. La segunda es el groupId de mi artefacto de dominio. Hago esto para no borrar toda la caché completa. Quiero solo borrar mi artefacto

3) desde la consola play

  update

4) Volver a eclipse y darle F5 al proyecto (o refresh)

Agregando los repositorios de la materia

Es conveniente además configurar play para que vea los repositorios maven de la materia. Así como lo hicimos al principio de la materia con el settings.xml para configurar Maven. En este caso configuramos play editando Build.scala
Eso se hace agregando dos resolvers:

    resolvers += ("Uqbar Releases" at "http://uqbar-wiki.org/mvn/releases"),

    resolvers += ("Uqbar Snapshots" at "http://uqbar-wiki.org/mvn/snapshots"),


Controller y Routes

Nuestros clases principales en play van a ser los controllers.
Un controller es una clase que extiende de play.mvc.Controller  y tiene métodos que van a ser invocados cuando un usuario haga un pedido al servidor (HTTP).

El método se debe encargar de:
  • Obtener los parámetros enviados por el cliente (en caso de que haya)
  • Ejecutar cierta lógica de dominio (por ejemplo obtener la lista de libros de la persistencia).
  • Generar una respuesta a devolver al cliente.
Siempre esos tres pasos.

Y cómo sabe Play qué método llamar y cuándo ?

Para eso existe una configuración especial de "rutas".

Routes

Varios frameworks funcionan de esta forma. Existe un archivo de configuración conf/routes
Aquí tendremos que registrar las URL's que sabremos atender, y vincularlas con un método de un Controller, que sabrá atender pedidos a esa URL.

URL  ->  Controller.método

El ejemplo más simple que viene con play es

GET     /                           controllers.Application.index()

  • La primer parte identifica el tipo de pedido HTTP (recuerden que puede ser POST, GET, PUT, DELETE, etc).
  • La segunda parte  es un pattern de URL, relativa al root del servidor. O sea, lógicamente no hace falta escribir http://servidor:puerto/algo, ya que ya nosotros somos el servidor. Solo escribimos lo que le sigue al server:puerto. Ej: /algo
  • La tercer parte es el nombre completo de la clase del controller y su método.

//TODO: dibujito acá

Métodos controllers - Hola Mundo

Implementemos el "hola mundo".
Para eso agreguemos una configuración para una nueva URL y método del controller.

GET /holaMundo controllers.Application.holaMundo()

Y en la clase del Controller:

public class Application extends Controller {
    public static Result holaMundo() {
    return ok("Hola Mundo");
    }
}

Acá podemos ver que un método del controller debe ser static, y debe retornar un objeto de tipo Result.
Para generar esos results, nuestros controller ya entienden varios métodos que podemos llamar, que nos dan los diferentes tipos de Result.

Qué es un Result ? Básicamente es una respuesta HTTP. El protocolo HTTP tiene un conjunto de respuestas posibles, con sus respectivos códigos. Algunos ejemplos:
  • 200 OK: indica al cliente (navegador por ejemplo) que el servidor procesó existósamente el pedido.
  • 400 Bad Request: el cliente cometió un error en el pedido (por ej no envió un parámetro requerido, etc).
  • 403 Forbidden: el cliente no tiene permisos para ejecutar esta funcionalidad.
  • etc.
Para cada uno de estos tipos de respuesta Play tiene métodos (factory methods) que podemos usar. Ej:
  • return ok()
  • return badRequest()
  • return forbidden()
También hay sobrecargas de estos métodos, por ejemplo ok("Hola Mundo") indica dos cosas
  1. Que es una respuesta de tipo 200 ok
  2. Que el cuerpo de la respuesta a enviar al cliente es el string "Hola Mundo".
Si vamos al browser entonces y pedidos la URL

http://localhost:9000/holaMundo

Veremos el hola mundo bien simplón.

Respuesta HTML

Los browsers entienden HTML como una formato de páginas web que permite tener contenido más "piola" que simplemente un String.
Así que una cosa que podemos hacer, es, desde el servidor generar HTML como respuesta.

    public static Result holaMundo() {
    String respuesta = "<html>" +
    "<head>" +
    "</head>" +
    "<body>" +
    "<h1>Hola Mundo!!</h1>" +
    "</body>" +
    "</html>";
    return ok(respuesta);
    }

Y ahora si pedimos de nuevo vamos a ver que se ve un toquecito más lindo.

En general nadie hace una aplicación de esta forma, porque tener mucho HTML mezclado en el código tiende a descontrolarse. Y después nadie podrá mantener eso.
Play entonces tiene un mecanismo para generar el html con templates.
Igualmente no vamos a entrar en detalle sobre esto, ya que en nuestra arquitectura el servidor no se va a encargar del HTML, sino que simplemente va a ser un servicio que nos va a devolver la información. Independientemente de cómo se muestre.

Mostramos esto para que vean que uno toma la decisión de arquitectura de dónde pone la vista.
Además, que el servidor puede devolver diferentes tipos de respuestas.

Métodos con Parámetros

En general nuestras aplicaciones van a generar contenido dinámico, y muchas veces en base a información que nos envíe el usuario/cliente.
Esa información son parámetros del pedido HTTP.

Evolucionemos un poquito el holamundo para que el cliente pueda pasarle el nombre de "a quien queremos que salude", y el server genere ese saludo.

Queremos usarlo así:

  • http://localhost:9000/hola/Pepe       ->   "Hola '''Pepe'''"
  • http://localhost:9000/hola/uis          ->   "Hola '''uis''"
  • http://localhost:9000/hola/uQbAr     ->   "Hola '''uQbAr'"
Para eso definimos la ruta como:

GET /hola/:aQuien controllers.Application.hola(aQuien:String)

Y agregamos el método en el controller:

    public static Result hola(String aQuien) {
    return ok("Hola '''" + aQuien + "'''");
    }

Se puede ver ahí que especificamos en la ruta un nombre para el parámetro, y luego su relación con el método.

Hay varias particularidades respecto a esto que se pueden ver en la documentación oficial de Play.


Arquitectura RIA (Rich Internet Application)

Una decisión de arquitectura al trabajar con aplicaciones web es pensar en la aplicación como dos partes separadas físicamente:
  • front-end (o parte cliente): es lo que corre en el navegador. En una arquitectura RIA, programamos esta parte con bastante comportamiento y manejo de estado. Es decir acá van a estar los objetos con lógica de navegación, selección, y visualización. Será como la Vista del MVC, mostrando objeto Modelo y con Controllers.
  • back-end (o parte servidor): eventualmente nuestros usuarios necesitan colaborar con otros usuarios, publicando contenido o al revés obteniendo contenido nuevo. Entonces, para eso la parte "cliente" necesita comunicarse con un "servidor" que se encargue de centralizar y mantener el estado compartido. En una arquitectura RIA, el backend no necesita (y tampoco estaría bien) estar acoplado con nada "visual", es decir, no necesita generar HTML, ni CSS's, ni nada por estilo. Simplemente es un proveedor de "contenido", de información (y también manipula ese contenido, claro).

Comparamos dos estilos de arquitectura web:

  Web Tradicional (Centrada en el servidor)RIA (Rich Internet Application) 
Cliente
  • Poco comportamiento. 
  • Hace pedidos al servidor esperando recibir páginas completas.
  • Tiene pocas responsabilidades. Incluso el servidor es quien maneja la navegación (a qué página ir a continuación).
  • El browser recibe HTML y lo muestra. 
  • Podría existir algo de comportamiento en código javascript, para hacer pequeñas validaciones
  • Más responsabilidades
  • Comportamiento de navegación
  • Encargado de la vista:
    • generar html
    • actualizar partes del html
    • definir cómo se muestran los datos.
  • Mantiene estado conversacional.
 Servidor
  • Casi todo el comportamiento.
  • Genera la vista (html)
  • Lógica de negocio.
  • Lógica de navegación y controllers (conversiones de tipos de datos, transformaciones para visualización de datos).
  •  Nada de responsabilidades de Vista o presentación de la información (no HTML, ni CSS)
  • Servicios web con persistencia de datos compartidos.
  • Lógica de negocio.

Podríamos ver la diferencia en una linea de asignación de responsabilidades


//TODO: hacer imágenes propias mejores


WebService REST+JSON en Play

Vamos a usar play2 ahora para implementar la parte "servidor" de nuestra arquitectura RIA.
Para eso sigamos por esta otra página.

Temas avanzados de arquitectura

Esto excede a los temas que vamos a ver en la materia, pero son links interesantes para profundizar en play, y en general en la compresión de nuevas arquitecturas de aplicaciones web.

Comments