Integración de Play con Slick y SQLite en Scala

Clase 28 de 36Curso de Scala básico

Antes de empezar, deberías descargar el template Play Scala Seed desde esta página: https://developer.lightbend.com/start/?group=play&project=play-scala-seed

Recuerda que para este proyecto usaremos IntelliJ IDEA como editor, así que asegúrate de tenerlo instalado junto con los plugins recomendados en una lectura anterior. Debes tener abierto el proyecto en el IntelliJ IDEA.

El proceso que seguiremos será el siguiente:

  • Instalaremos las dependencias de Slick y el Driver de la base de datos.
  • Agregaremos las configuraciones para la conexión con la base de datos.
  • Crearemos un archivo Movie.scala con el modelo y la definición de la tabla.
  • Haremos el esqueleto de funciones para la clase siguiente.
  • Agregaremos una función de ayuda para crear la base de datos.

Dependencias

Lo primero que hay que agregar son las dependencias para usar PlaySlick (el plugin que integra Play con Slick), y el driver JDBC de la base de datos que vayamos a usar, que en este caso será SQLite.

SBT buscará los paquetes de sus dependencias en Maven.

Puedes pensar en Maven como algo similar a NPM de JavaScript (un repositorio de paquetes que pueden ser descargados). Aunque la diferencia con NPM es que Maven no constituye una entidad única si no múltiples repositorios de paquetes administrados por diferentes entidades o empresas.

Por eso para encontrar nuevos paquetes existe una página que tiene indexados más de mil repositorios de Maven: https://mvnrepository.com/

Allí es fácil hacer búsquedas no solo de los paquetes que necesitemos, si no también de sus versiones disponibles.

Como ejercicio, haz tú la búsqueda de los dos paquetes que queremos instalar. En la siguiente imagen puedes ver cómo debe quedar el build.sbt:

01.png

Luego con el comando sbt update desde la consola, haremos que sbt descargue estas dependencias en nuestro proyecto para que estén disponibles.

Configuración

Lo siguiente que debes hacer, es configurar los datos de acceso a una base de datos SQLite. Esto lo haremos en el archivo application.conf que se encuentra en la carpeta conf del proyecto.

02.png

El archivo application.conf está escrito en un formato llamado HOCON. En la imagen estamos definiendo las configuraciones para la base de datos por defecto (Slick permite configurar múltiples conexiones al tiempo también). Las configuraciones mínimas son las siguientes:

  • Profile: Le dice a Slick el perfil de la base de datos que vamos a usar. Hay que notar el símbolo $ al final de la clase SQLiteProfile. Puedes ver un listado de las bases de datos soportadas con los links a sus perfiles en la documentación de Slick, https://scala-slick.org/doc/3.3.1/supported-databases.html
  • Driver: Es el driver que Slick usará para comunicarse con la base de datos. JDBC es un estándar para controladores de bases de datos en Java. Todas las bases de datos populares tienen un driver JDBC y en nuestro caso, será el path al que instalamos como dependencia.
  • Url: Es la dirección de conexión a la base de datos. Para el caso particular de SQLite, MyDataBase.db será el nombre del archivo donde se alojará la base de datos. No es necesario que exista con antelación ya que más adelante haremos que play lo cree por nosotros. En otras bases de datos como Postgres, Oracle o MySql, la URL de conexión será distinta, pero siempre siguiendo su formato para JDBC.
  • User y Password: Para este proyecto, nuestra base de datos en SQLite no tiene ni usuario ni contraseña, lo hacemos así por simplicidad pero en proyectos reales tu base de datos debe tener usuario y clave seguros.
  • El último parámetro (slick.default), hace referencia al paquete de Scala donde Slick buscará las clases cuando hagamos la definición de los modelos.

Modelo

En la carpeta app, crearemos un paquete que se llame models, y dentro crearemos un archivo llamado Movie.scala. Puedes crearlos con clic derecho New, o creando la carpeta y el archivo manualmente. Debe quedarte de la siguiente manera:

03.png

Case Class Movie

Lo primero será convertir la clase Movie en un case class, y agregarle los atributos correspondientes. Este case class debe quedar de la siguiente manera:

04.png

Los puntos a notar son:

  • Usamos paréntesis () en vez de llaves {}.
  • Los atributos id y tags son opcionales, pero el primero crea por defecto un UUID (que debes de importar) cuando el objeto no tenga id.

MovieTable

Teniendo ya el case class, debemos crear una clase que será el "puente" entre la información que se envíe/reciba de la base de datos, hacia el objeto de Scala.

Esta clase la llamaremos MovieTable y puede estar en el mismo archivo, junto con la case class Movie (a diferencia de Java, en Scala podemos tener distintas clases en el mismo archivo sin problema). Debe quedar de la siguiente manera:

05.png

  • Se debe importar el contenido del objeto api que está en el profile de nuestra base de datos SQLite (en Scala el guión bajo _ en una importación, es equivalente al asterisco * de Java o Python).
  • La clase extiende de un trait llamado Table[T] que recibe un tipo de dato T, que en este caso será el case class Movie. Allí también de le dice el nombre de la tabla de SQL a la que hace referencia.
  • Cada atributo se define con un tipo column, dándole el tipo de dato de Scala, y el nombre de la columna en SQL.
  • Algunos atributos tienen opciones especiales, como el id que tiene O.PrimaryKey, o tags que tiene O.Length(2000, varying = true) . Existen varias opciones que están dentro del objeto O de opciones de columna. Las opciones disponibles siempre depende de la base de datos que estés usando.
  • La definición del id en la tabla es directamente un String en vez de un Option[String]. Esto es porque la clave primaria nunca puede ser nula.
  • Hay una definición especial que describe la proyección entre el objeto tabla y la case class a la que mapea. El nombre que tiene es un asterisco *. Para el atributo id.?, ese ? es necesario porque en la definición de la tabla ese atributo es obligatorio, pero en la definición del objeto es opcional, de esa manera el compilador sabe que lo debe transformar.

Si quieres conocer más detalles sobre esto, puedes verlos en la documentación de Slick: https://scala-slick.org/doc/3.3.1/schemas.html

MovieRepository

La última parte en este archivo será lo que se conoce como repositorio.

En este contexto, un repositorio es el encargado de comunicar la base de datos con lo que será más adelante el controlador. Esta será la clase sobre la que vamos a trabajar en la próxima clase.

El repositorio debe quedar de la siguiente manera:

06.png

  • Hay varias clases que es necesario importarlas, y que luego serán "inyectadas" haciendo uso de un patrón conocido como Inyección de dependencias. No es el objetivo explicar eso aquí, lo importante es entender que una instancia esas clases va a estar disponible dentro de la clase repositorio luego de ser inyectada.
  • El objeto inyectado dbConfigProvider contiene lo necesario para realizar peticiones a la base de datos, usando los datos que escribiste en el application.conf.
  • El contexto de ejecución es usado para realizar las peticiones de manera asíncrona. Más adelante veremos esto en detalle. Se usa la palabra reservada implicit para no tener que pasarle este parámetro todo el tiempo, si no que el compilador lo ponga por nosotros.
  • El atributo movieQuery, es desde el cual vamos a estar creando las peticiones a la base de datos en la próxima clase.

Funciones de ayuda para la creación de la base de datos

Esta parte no la explicaré en detalle, pero será útil para crear el esquema de la base de datos usando lo que acabamos de escribir. Es decir, que no es necesario tener las definiciones de las tablas en SQL, si no que con base en el código en Scala, podemos decirle a Slick que las cree por nosotros.

En el repositorio

Para ello, primero debes agregar el siguiente código dentro de la clase MovieRepository:

// Esto va dentro de la clase MovieRepository /** * Función de ayuda para crear la tabla si ésta * aún no existe en la base de datos. * @return */ def dbInit: Future[Unit] = { // Definición de la sentencia SQL de creación del schema val createSchema = movieQuery.schema.createIfNotExists // db.run Ejecuta una sentencia SQL, devolviendo un Future db.run(createSchema) } def getAll = ??? def getOne = ??? def create = ??? def update = ??? def delete = ???

Nota que la última parte en donde están los ???, es donde empezaremos en la próxima clase.

En el HomeController

Luego debes agregar lo siguiente en el archivo app/controllers/HomeController.scala

// Importar el repository y el contexto de ejecución global import models.MovieRepository import scala.concurrent.ExecutionContext.Implicits.global //.... // Hay que agregar el repository para que sea inyectado @Singleton class HomeController @Inject()( cc: ControllerComponents, movieRepository: MovieRepository ) extends AbstractController(cc) { // ..... /* Función de ayuda para crear la tabla si esta aún no existe. */ def dbInit() = Action.async { request => movieRepository.dbInit .map(_ => Created("Tabla creada")) .recover{ex => play.Logger.of("dbInit").debug("Error en dbInit", ex) InternalServerError(s"Hubo un error") } } }
  • Se importa el repositorio.
  • Se coloca el repositorio en la cabecera de la clase HomeController para que sea inyectado.
  • Se define la acción dbInit.

En el archivo de rutas

Por último se debe agregar en el archivo conf/routes una ruta hacia la acción dbInit que acabamos de agregar:

GET /dbInit controllers.HomeController.dbInit

![07.png](https://static.platzi.com/media/user_upload/07-a0159219-ea8b-41fa-993d-3eadd97438e7.jpg

Corriendo el servidor y creando la base de datos

Usaremos en comando sbt compile para estar seguros que todo compila correctamente, y después escribimos el comando sbt run para correr el servidor en modo desarrollo.

Con el servidor de desarrollo encendido, visitaremos la dirección http://localhost:9000/dbInit, y después de unos segundos, deberías ver el mensaje de "Tabla creada".

08.png

Y si revisas la carpeta del proyecto, deberías ver el archivo de la base de datos SQLite también:

09.png

Para detener el servidor de desarrollo, usa las teclas Ctrl+D en la consola.

Conclusiones

En esta clase escrita aprendimos muchas habilidades útiles para trabajar con Play y Scala.

Aquí fuimos a lo práctico, pero por supuesto hay temas que se profundizan en la documentación de Play y que quizás te puedan interesar: https://www.playframework.com/documentation/2.7.x/PlaySlick

Estoy seguro que en el código de esta clase notaste cómo aplicamos varios de los temas que vimos en las clases pasadas. Escribe en los comentarios cuales viste que te llamaron la atención, y por qué crees que son útiles o inútiles en donde fueron aplicados.