• facebook
  • flickr
  • google
  • instagram
  • lastfm
  • linkedin
  • spotify
  • twitter
  • Goodreads
  • GitHub
  • juanignaciosl.github.io
  • Medium
Cómo se carga una página en mi uso de AngularJS, Play, Slick… (II – servidor)

Cómo se carga una página en mi uso de AngularJS, Play, Slick… (II – servidor)

En la anterior entrada expliqué, grosso modo, lo que ocurre en el navegador del usuario en una aplicación de monitorización de sensores en tiempo real con AngularJS. En esta segunda edición toca revisar el papel del backend preparado para que Nemesis pueda hacer estudios de optimización y eficiencia energética.

Obviaré la carga inicial y los recursos estáticos, comentados en la anterior (incluyendo la integración de la compilación de Sass y de Javascript).

Como ejemplo tomaré uno de los últimos requisitos implementados: la configuración de tarifas. Si vas a tener una aplicación que monitoriza tus consumos (eléctricos, de gas, etc), una aplicación directa es calcular los costes (especialmente interesante ahora que van a aplicar la nueva normativa). Una tarifa eléctrica se define por tramos horarios a lo largo de una semana. Para hacer esta planificación usé el plugin jQuery fullcalendar, a través de UI Calendar (su recubrimiento para AngularJS). Como dijo un buen amigo, cuando haces calendarios, todo son edge cases, pero la verdad es que este plugin ha sido una ayuda inestimable.

API REST: Play Framework

El servidor se encarga de proveer al cliente datos en formato JSON  mediante un API REST.

Como ya comenté, el punto de entrada para Play es el fichero routes.conf que, con tipado (olvídate de esos ficheros de configuración en texto plano y sus errores en ejecución), establece qué método de qué controlador atiende qué peticiones en qué rutas, con qué parámetros. Estas son las dos líneas esenciales para esta página:

Como puedes suponer, /tariff identifica el conjunto de todas las tarifas existentes, por lo que un GET devolverá todas y un POST añadirá una nueva. El naming a la hora de diseñar un API REST es muy importante para no acabar convirtiéndose en un engendro sin sentido, y he procurado seguir las recomendaciones del famoso tutorial de Pearson eCollege. Hay ciertas zonas grises de difícil solución (por ejemplo, ¿es coherente que, por cuestión de autorizaciones de acceso, un mismo URI identifique conjuntos de datos diferentes?), pero un mínimo de coherencia es fundamental por higiene mental ;-)

La página, al cargar, lanza el GET sobre /tariff para poblar el desplegable de tarifas a editar. Este es el método:

Buena parte será legible aunque no estés familiarizado con Scala y Play. Viene a decir lo siguiente:

Siempre que haya un token identificamos al usuario (ver “Seguridad”) y atendemos la solicitud. Para ello, consultamos al servicio de tarifas (al que le inyectamos la base de datos) aquellas gestionadas por él (ver “Lógica de negocio” y “Acceso a base de datos”). Si esta consulta acaba en un tiempo razonable (ver “Futuros”), las devolvemos como JSON. Si no, devolvemos un error de servidor.

La conversión de JSON se hace con mapeo automático (la declaración, con un par de excepciones, también lo es). He optado por hacer clases específicas para el paso de datos al cliente, ya que a menudo es necesario pasar árboles de objetos o agregaciones que no tienen por qué tener un reflejo exacto en los modelos que mapeamos en la base de datos.

Seguridad (autenticación y autorización)

Vengo del mundo JEE, y en él “seguridad” suele equivaler a establecer sesiones de usuario en el servidor (algo que cité como un problema de base en el diseño de JEE para aplicaciones web). Cuando tienes una aplicación cliente-servidor pura, single-paged, con Javascript y REST, lo normal es basarse en una cookie que almacena un token y que es enviada explícitamente con el Javascript en cada petición en lugar de ser “leída por el servidor”. El servidor mantiene un listado de tokens conocidos asociados a usuarios, con lo que puede autenticar y autorizar. Como sólo Javascript del mismo dominio puede leer la cookie, se evita el problema de Cross Site Request Forgery (XSRF). Para más información tienes el fantástico artículo de James Ward.

Este patrón está implementado en AngularJS, en $http. Mediante la cookie X-XSRF-TOKEN se recuerda el token (para no tener que meter las credenciales constantemente) y con el encabezado X-XSRF-TOKEN se envía el token previamente enviado por el servidor en el login. Marius Soutier tiene un artículo y código de ejemplo implementando el lado servidor en Play (el “voy a hacer algo pero alguien lo ha hecho por mi antes” ha sido una agradable constante en el proyecto :-) ). Con una ligera adaptación tenía la autenticación de usuarios lista, y con poco más para persistir a base de datos (el ejemplo usa una caché en memoria) tendré el mismo mecanismo para autenticar los sensores que envían la información.

El método HasToken ha quedado como algo así:

Este método sirve para pasarle una función que será ejecutada, recibiendo el identificador del usuario, si localiza el token. Su declaración puede resultar algo marciana, así que me detendré un poco más en ella (el cuerpo es muy sencillo). Es una “higher-order function” (recibe una función por parámetro) con la que wrappearemos el código en cuestión. La función utiliza currying, declarando por separado los argumentos. En la primera lista recibe un único argumento, un BodyParser, con un valor por defecto (de hecho nunca he necesitado pasar un valor). En la segunda se define la función que recibe (la que queremos wrappear). Este se ve mucho más claro si ponemos unos paréntesis:

Recibe, por tanto, una función que, a partir de una cadena (el token) genera una función que a partir de un Long (el identificador de usuario) genera una función que, a partir de una solicitud, genera un resultado (más exactamente, un futuro de un resultado). Así dicho suena a chiste, pero si ves su uso, arriba, verás que el código resultante es muy claro y conciso. Este es un claro ejemplo de cómo aplicar las ventajas funcionales de Scala, con una aproximación orientada al objeto habríamos tenido que resolverlo de una forma totalmente diferente.

Lógica de negocio

El método que genera las tarifas de usuario, userManagedSensorTariffs, es el siguiente:

Utiliza una for comprehension. Esta es una expresión súper útil en Scala, que en realidad es azúcar sintáctico sobre otras operaciones funcionales como map, flatMap o filter. La lectura es clarísima: para cada localización de la que el usuario es administrador toma los sensores que están en ella, y saca las tarifas. La expresión genera una nueva colección, de la cual tomamos sólo las diferentes. Y, como todas las operaciones de consulta sobre los repositorios devuelven futuros, el resultado final es a su vez un futuro.

¿Y por qué un futuro?

Básicamente todos los métodos de mis lógicas de negocio devuelven un futuro. Concretamente, todos los que realizan una operación potencialmente costosa (como un cálculo complejo o una consulta a base de datos). Con un coste de programación bajo (con las construcciones de Scala y Play hacerlo se reduce a poco más que modificar los valores de retorno de los métodos) conseguimos reducir al mínimo los bloqueos en el servidor, aumentando la escalabilidad. Sadek Drobi, uno de los co-creadores de Play, tiene un gran artículo sobre asincronismo.

Acceso a bases de datos con Slick

Cuando hice la web de registro de ofertas de viaje personalizadas el acceso a base de datos lo hice con Anorm, que es poco más que JDBC. Pero como no hay cosa que más me aburra que escribir SQL, y además reutilizar SQL es muy complicado (no está hecho para ser compuesto, así que intentar no duplicar suele acabar generando ilegibles concatenaciones de cadenas  o ORMs caseros) para un proyecto importante he recurrido a algo mejor: Slick. Sirve para consultar una base de datos de forma funcional, con operaciones como map o filter. Como ejemplo, este es el método locationsForUser:

Si el usuario es administrador de una localización (digamos, un edificio) transitivamente es administrador de todas las localizaciones contenidas (digamos, pisos). Paca calcular esto el método consulta la tabla de localizaciones-roles-usuarios, filtra por rol y usuario, saca las localizaciones y después añade todo lo contenido en cada una. No os pongo el sql que genera esto porque os arrancaríais los ojos, pero confiad en mi si os digo que esto no son iteraciones, sino wheresjoins y demás. ¿Y cómo sabe de dónde sacar esto? De un mapeo:

Antes de que, como buen purista, hayas empezado a poner un comentario acerca de los ORMs y el impedance mismatch, ten en cuenta que aquí no se da, como se explica en la presentación “Slick vs. ORM”. Slick es mucho más explícito que, digamos, Hibernate, pero proporciona buena parte de las ventajas de productividad y seguridad de tipos de él y del resto de proyecto.

El paréntesis detrás del bloque es el contexto donde se ejecuta. Un contexto aquí es un pool de hilos. La aplicación define cuatro pools diferentes, de forma que aislamos los bloqueos (por ejemplo, la entrada/salida que provoca la base de datos, al no haber drivers reactivos confiables para PostgreSQL). Esto está relacionado con el apartado anterior, y se explica en el artículo referenciado.

Conclusiones

En general diré que esta pila de Typesafe me encanta (de hecho mi siguiente proyecto web ya lo estoy empezando con ella):

  • Scala es un lenguaje muy atractivo (aunque complejo). Me he vuelto adicto a las construcciones funcionales map, flatMap, filter…, opciones y futuros.
  • La programación funcional es una forma muy natural de resolver ciertos problemas (aunque apenas estoy rozando la superficie).
  • Play en si tiene muy poco protagonismo, siendo un ligero pegamento entre Akka y Slick, proporcionando todo lo necesario para JSON, WebSockets, pool de conexiones, etc. Su flujo de trabajo, con recarga en caliente, es bastante productivo.
  • Slick, siendo muy diferente a Hibernate, me proporciona todo lo que necesito para no tener que escribir SQL.

Como contra, creo que todavía tienen que pulir ciertas aristas (de hecho, varias están siendo abordadas en Play 2.3, Slick 2.0.2…)

  • Play está orientado a ser “reactivo por defecto”, pero lo que hay que hacer para “trabajar siempre con futuros” me parece a menudo demasiado explícito y engorroso (aunque debería poder simplificar con código propio).
  • Mucho boilerplate code en las acciones cuando trabajas con futuros y JSON para el control de errores. No sé hasta qué punto es cosa mía y cuánto de Play, tengo que pulir esto.
  • Scala-IDE (Eclipse) a menudo es un cuello de botella de tiempo más que considerable (no consigo dar con una configuración adecuada).

PD: ¿has leído el reactive manifesto? Explica la filosofía detrás de todos estos componentes.

Pantalla de establecimiento de tarifas

¿Me dejas una respuesta?