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.