Integración de Play con Slick y SQLite en Scala
Clase 28 de 36 • Curso 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:
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.
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 claseSQLiteProfile
. 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:
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:
Los puntos a notar son:
- Usamos paréntesis
()
en vez de llaves{}
. - Los atributos
id
ytags
son opcionales, pero el primero crea por defecto un UUID (que debes de importar) cuando el objeto no tengaid
.
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:
- 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 datoT
, que en este caso será el case classMovie
. 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 tieneO.PrimaryKey
, otags
que tieneO.Length(2000, varying = true)
. Existen varias opciones que están dentro del objetoO
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 unString
en vez de unOption[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 atributoid.?
, 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:
- 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".
Y si revisas la carpeta del proyecto, deberías ver el archivo de la base de datos SQLite también:
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.