Clean and re-usable Slick modules
This is the second post in a three part series about our database access layer and patterns we’ve adopted. If you missed the first, read about some of the patterns we use when working with our databases.
We’ve talked previously about how we’ve abstracted away our database backend and query/access libraries. Unfortunately, the recent Slick 2.0 upgrade meant our previous abstraction and every Repo (63 at last count) needed to be upgraded. So, we had a fix-it morning and had Slick 2.0 in production by lunch. Huzzah!
This post gets into more implementation details about our database access patterns. Some of this overlaps Eishay’s talk about how we used Slick 1.0, and updates the code he shared to Slick 2.0.
Our code that accesses the database happens in a Guice injected class called a Repo. Our product, Kifi, handles very large imports of links that people want to become keeps. In order to protect our users’ data, we persist the raw links as fast as possible in a batch insert, before we normalize the URL (so https://www.kifi.com is the same as http://kifi.com/), de-duplicate, run our scraper service, etc. Here’s part of the RawKeepRepo implementation:
RawKeepRepo comes automatically with a few helpful methods: save(m: RawKeep), get(id: Id[RawKeep]), etc. How does that work? All of our repos extend DbRepo[T], which defines some basic functionality of repos.
That’s a lot to take in, so we’ll break it down. In RawKeepRepo, we inject a DataBaseComponent which wraps the database driver and dialect. We then implement a RawKeepTable that extends RepoTable, which provides definitions of columns not automatically provided (such as userId, url, etc), as well as the * binder that Slick needs. We also define a def table that takes a tag (which Slick provides) and returns an instance of the table.
You may notice that all public methods in the DbRepo take an implicit RSession. This is actually a trait, extended by RWSession and ROSession. We define sessions as read-only or read-write outside of the Repo level, so we can be transactional when necessary and combine several queries in one connection.
What does this give us? An instance of RawKeepInfoImpl has common columns like id, createdAt, updatedAt, and state defined automatically, so we’re able to keep our classes small. Additionally, it has convenience getters and setters implemented already, such as rawKeepRepo.get(someRawKeepId). Using the repo is quite easy:
Next time, we’ll combine all of this together in a sample project you can play around with.
We wrote this post while working on Kifi — tools that help people outsmart information overload together. Learn more.
Originally published at eng.kifi.com on February 27, 2014.