Home

Slick codegen and Scala.js

Since Scala.js came out, I've wanted to use the Slick codegen module to generate my data model and share classes between the client and server. It took me a while to reach a setup I liked, below are some of my lessons.

TL;DR. Haven't found a perfect solution yet, but working example is on Github.

This post assumes you have knowledge of both sbt and Slick, no knowledge of Scala.js is required.

Setup sbt

Let's begin, I have some SQL code from which I want to generate boilerplate Slick mapping. My sbt project looks like this

build.sbt                    <-- second step
project/
    plugins.sbt              <-- first step
    Codegen.scala            <-- third step
server/
    src/models/Tables.scala  <-- autogenerated
    conf/db/evolutions/1.sql <-- database stuff
shared/
    src/models/Tables.scala  <-- autogenerated
    src/models/Time.scala    <-- inherits java.util.Date
client/
slick-driver/
    src/slick/Driver.scala   <-- custom driver
server-codegen/              <-- empty project to run server codegen
shared-codegen/              <-- empty project to run shared codegen
flyway/                      <-- empty project to run migrations

This seems like a lot of projects. Don't fear, creating projects in sbt is easy.

The first step is to add necessary plugins to plugins.sbt, most importantly sbt-slick-codegen.

The second step is to define the projects in build.sbt. You don't necessarily need all the bells and whistles from that build definition, but the project definitions are required.

We're done with the boring part.

Codegen

The third step is to write code that generates even more code! We write this in project/Codegen.scala. This section has much room for improvements, but I think my findings below are not too bad. I struggled most with dates, custom type mapping and using a custom driver.

Map Timestamp to java.util.Date

I want to map timestamp columns to something that runs both on the JVM and JavaScript. My solution was to create a simple wrapper Timestamp that inherits from java.util.Date.

The benefit to this approach is that Timestamp is a case class. Case classes play well with Slick's * method and also make it possible to use libraries such as upickle.

The downside to this approach is that I created Yet Another date class for my own project. Ideally, these should be part of some reusable library.

To get a richer date API, provide an implicit conversion to Joda-Time on the server and Moment.js on the client.

Reuse custom type maps

I want to reuse my custom type mappings between the server and shared code generators. The SourceCodeGenerator API makes this trickier than I expected (or maybe my trait foo isn't good enough). Eventually, I settled on using trait mixins, see PostgresColMap.

I haven't tried to map more custom types than text[] to List[String]. I imagine that the colMap will grow alongside your application.

Use custom slick-pg driver

Skip this part if you don't want to use the latest and greatest features in PostgreSQL.

Fortunately slick-pg exposes many of PostgresSQL's best features with Slick. Unfortunately, there isn't a good way to use these features on H2 during testing/development.

For me, the trade-off is worth it. I put lazy val profile = is.launaskil.slick.Driver into the server Tables.scala instead of val profile: JdbcProfile. I won't look back (but let me know if you find a solution ;)).

Note. You must publish the Slick driver locally before running the app, Play's custom class loaders will otherwise not find the driver, see #266.

Run the app

Excellent, everything is almost complete. The fourth step is to run the app.

First, you need to start the database. I use docker (on OSX) and run the following command

$ docker run -p 5432:5432 --name launaskil-dev -e POSTGRES_PASSWORD=postgres -d postgres

Next, I source the database connection parameters and fire up sbt like this

$ source source_me && sbt

Next, I run migrations with Flyway, as demonstrated in the sbt-slick-codegen-example.

[server] $ flyway/flywayMigrations

Next run p to publish the slick driver and then cg (short for "codegen") to run the code generation. These aliases are defined in build.sbt.

[server] $ p
[server] $ cg

Take a look at the generated server/.../Tables.scala and shared/.../Tables.scala. The server file should contain only Slick table mappings and the shared file should only contain case classes.

To verify the project compiles, execute run in the sbt console and visit localhost:9000. The browser should display a message like

List(AppUserRow(1,Some(Sun Nov 14:14:09 GMT+0100 2015),None,List(a)))

In summary

That's a wrap. We just managed to use Slick codegen to generate our data model from SQL such that we can share classes between the client and server. The solution supports dates, custom type maps and custom drivers.

It would be neat to put some of this stuff into an sbt plugin.

PS. You'll see is.launaskil sprinkled around the app. Launaskil.is is a website I wrote last summer and the examples are borrowed from the app's closed source.

Home