Marco Ordoñez

PlayFramework y Slick (Scala)

Desde hace buen tiempo atrás estoy trabajando con Scala y la experiencia es súper agradable. Decidí meterme de lleno por los muchos beneficios que tiene y lo agradable que es el lenguaje a nivel síntactico.

A continuación listaré algunos de los beneficios de Scala para los que no conocen sobre el lenguaje y los invito a escribirme (mordonez.me@gmail.com) en caso tengan alguna duda.

Algunos beneficios de trabajar con Scala

Multi Paradigma: Si bien es cierto no es el primero lenguaje que lo ofrece, su parte funcional está mucho más desarrollada que otros lenguajes como Python por ejemplo.

Tipado Éstatico: Cuenta con tipado estático descubriendo los errores en tiempo de ejecución (mas información en: Wikipedia - static and dynamic)

Inferencia de tipos: El compilador puede inferir los tipos sin necesidad de escribirlos. Esta caracteristica nos permite escribir el codigo mas compacto sin perder la seguridad que compilará correctamente o fallará en caso de algún error.

Interoperabildidad con Java: Puede integrarse con otras librerías de Java, esto no es recomendado sin respectivos wrappers ya que los NullPointerException son expuestos y Scala plantea una muy buena forma de tratar esos problemas (buscar: Option, Some y None).

Las expuestas lineas anteriores son algunos de los beneficios del lenguaje, sin embargo quiero comentar tambien que Typesafe (la empresa detrás de Scala) cuenta además con otras soluciones muy buenas en su stack como son Akka, Spray, Slick, Sbt y Spark Typesafe platform).

El propósito con este tutorial es brindar una guía rápida de como poner en marcha una aplicación básica con Play y Slick en pasos sencillos y sin videos o hangouts elaborados de una hora o más (incluso más largos que los del propio typesafe intro to play by typesafe) de personas que a mi parecer saben "un poco de todo y mucho de nada".

Bueno, ahora si, sin más preambulo empecemos con el tutorial. Para este ejemplo vamos a crear una aplicación simple que tendrá una pagina donde listaremos libros y sus autores y lo haremos usando play y slick para conectarnos a la base de datos, que en este caso será mysql.

Creando una aplicación Playframework/Slick

Dependencias

  • JDK 8
  • Creación de base de datos Mysql con nombre playslickbasic.

Paso 1: Descargando playframework

Ingresamos a la web de typesafe y descargamos la versión minimal, pueden descargarlo haciendo click aquí.

Una vez descargado deben descomprimirlo y ponerlo en un lugar adecuado para luego agregar esa ruta a la variable de entorno PATH y que el comando esté disponible. Los pasos para hacer esto los pueden ver en la web de typesafe haciendo click aquí.

En versiones anteriores play era distribuido de forma diferente, hoy se ha renombrado a activator como comando (antes era play) y además de crear proyectos play puede activar plantillas predetermindas a modo de ejemplo para los que se están iniciando. Mas información sobre las plantillas aquí, cada plantilla tiene su propio tutorial e incluso pasos de como ponerla en marcha, por tal razón no trataré el tema. Si alguno tiene problemas activando alguna puede escribirme.

Paso 2: Creando una aplicación y analizando la anatomía.

En este paso se asume que el framework está correctamente configurado y el comando activator está disponible.

Ejecutamos el comando activator new play-slick-basic, este comando crea una nueva aplicación con el nombre "play-slick-basic".

Una vez hecho esto podemos ver que se ha creado un folder llamado "play-slick-basic" con la estructura que aparece en la siguiente imagen:

Exploremos una carpeta a la vez:

app: Es donde almacenaremos todos nuestros controladores, vistas y modelos. Como se puede ver en la foto viene con algunos archivos por defecto para iniciar la aplicación básica.

conf: Esta carpeta contiene los archivos de configuración que necesitemos (resources) y el archivo de configuración de rutas (routes).

project: Esta carpeta es heredada de la configuración de sbt (mas información en http://www.scala-sbt.org/) y contiene información sobre el proyecto sbt, por ejemplo versión de sbt, los plugins sbt que se usarán, entre otros.

public: En esta carpeta se almacenan todos los archivos estáticos.

test: Carpeta para pruebas unitarias.

Paso 3: Creando un modelo, una vista y un controlador.

Pasos previos

Debemos incluir las librerias que vamos a usar, en nuestro caso usaremos slick para la conexión a la base de datos. El lugar donde debemos definir las librerias del proyecto es en el archivo build.sbt en la propiedad libraryDependencies (mas información aquí).

Reemplazamos nuestras dependencias de la siguiente forma y dejamos solo al conector de mysql y a slick como dependencia:

libraryDependencies ++= Seq(  
   "mysql" % "mysql-connector-java" % "5.1.18",
   "com.typesafe.slick" %% "slick" % "2.1.0"
)

Además tambien debemos crear una conexión a la base de datos, como nuestro controlador usará la conexión para obtener valores debemos de crearla en algún lugar del proyecto. Play recomienda manejar las configuraciones globales en un archivo llamado Global.scala (mas información aquí) que deberá estar situado dentro de la carpeta app, osea en el paquete por defecto.

Antes de crear nuestro archivo Global.scala debemos definir los valores de conexión en nuestro archivo conf/application.conf.

db.default.driver = com.mysql.jdbc.Driver  
db.default.url="jdbc:mysql://localhost/play_slick_basic"  
//Deben reemplazar los valores por el usuario y password que utilicen en desarrollo.
db.default.user=root  
db.default.pass=root  

Luego crearemos el archivo app/Global.scala y deberá quedar de la siguiente manera:

import play.api.GlobalSettings  
import play.api.Application  
import play.api.Play.current  
import com.typesafe.config._  
import scala.slick.driver.MySQLDriver.simple._  
import models._

object Global extends GlobalSettings{

  override def onStart(app: Application) {
    //Creamos una sesión
    globals.database.withSession { implicit session =>
      //Solo para el ejemplo hemos utilizado el try y catch para saber si las tablas fueron creadas
      try{
        //Creamos las tablas, los metodos ddl contienen los query de creación y el metodo create los ejecuta
        (Author.ddl ++ Book.ddl).create  

        //Creamos un registro de Author con el metodo +=
        val author = Author += Author(None, "Martin Odersky")
        //Creamos un registro de Author con el metodo += y pasamos la instancia de author como foreignKey
        Book += Book(None, "Programming in Scala", author)

      } catch {
        case _:Throwable => println("Tables already created")
      }
    }
  }
}

package object globals {

  //Cargamos la configuración definida en nuestro archivo application.conf
  val dbUrl = ConfigFactory.load().getString("db.default.url")
  val driver = ConfigFactory.load().getString("db.default.driver")
  val user = ConfigFactory.load().getString("db.default.user")
  val password = ConfigFactory.load().getString("db.default.pass")

  //Creamos una conexión al base de datos con los datos definidos en nuestro archivo application.conf
  lazy val database = Database.forURL(dbUrl, driver = driver, user = user, password = password)

}

Creación de un modelo

En la carpeta app creamos la carpeta models y agregamos un archivo con el nombre Model.scala. Escribiremos dos modelos en el archivo, unos para los libros y otros para los autores y crearemos una relación entre ellos.

El código del archivo debe quedar de la siguiente manera:

package models

//Import necesario para exponer las clases que necesitamos para nuestros modelos
import scala.slick.driver.MySQLDriver.simple._

//Case class que servirá para representar a nuestros registros de la tabla Author de la base de datos.
case class Author(id:Option[Int], name:String)

/* Representación para Slick de una tabla en la base de datos. Contiene las columnas 
y un valor * necesario para Slick con el mismo tipo de las columnas de la tabla */

class AuthorTable(tag: Tag) extends Table[Author](tag, "Author") {  
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def name = column[String]("name")
    def * = (id.?, name) <> ((Author.apply _).tupled, Author.unapply)
}

//Objeto Author con el cual haremos las operaciones en la base de datos
object Author extends TableQuery(new AuthorTable(_))

case class Book(id:Option[Int], name:String, authorId:Int)  
class BookTable(tag: Tag) extends Table[Book](tag, "Book") {  
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def name = column[String]("name")
    def authorId = column[Int]("authorId")
    def * = (id.?, name, authorId) <> ((Book.apply _).tupled, Book.unapply)

    //Llave foranea para la tabla Author.
    def author = foreignKey("author_fk", authorId, Author)(_.id)
}
object Book extends TableQuery(new BookTable(_))  

Creando un controlador

En la carpeta app ya tenemos una carpeta llamada controllers con un archivo de ejemplo que viene con el framework. Podemos usar el archivo y utilizar la función index que viene por defecto, solo debemos modificarla para obtener una lista de libros (instancias Book) de la base de datos, para eso modificamos nuestro archivo para que quede de la siguiente manera:

package controllers

import play.api._  
import play.api.mvc._  
//Referencia a la conexión a la base de datos en el archigo Global.scala
import globals.database  
//Necesario para el metodo list en (books.list)
import scala.slick.driver.MySQLDriver.simple._  
import models._

object Application extends Controller {

  def index = Action {

    database.withSession { implicit session =>

      //Obtenemos la lista de libros de la base de datos
      val books = (for(b <- Book) yield b).list

      //Enviamos la lista a la vista para que se muestre
      Ok(views.html.index(books))
    }   
  }

}

Creando una vista

En nuestro caso por simplicidad usaremos la vista que viene por defecto y como hicimos con el controlador, la modificaremos para que muestre en una lista no ordenada html la lista de libros que tenemos en la base de datos. Para eso debemos ingresar a apps/views y modificar el archivo index.scala.html y debe quedar de la siguiente forma:

@(books: List[Book])
<ul>  
@for(b <- books){
  <li>@b.name</li>
}
</ul>  

Como se puede ver, la plantilla recibe un parametro del tipo List[Book], todas las plantillas pueden recibir parámetros ya que son funciones que luego el compilador se encarga de escribir. De esta forma, incluso las plantillas son typesafe.

Ahora que ya tenemos el proyecto básico listo, podemos arrancarlo ejecutando activator "~run".

Los archivos del tutorial los pueden descargar desde github en el siguiente enlace:

https://github.com/mordonez-me/play-slick-basic.

Marco Ordonez