Longevity Release 0.23 - Use Type Classes to Improve Type Safety of Persistent API

This longevity release has been a long time in the making. The main improvement here, and the one that has taken by far the most effort, is instituting the use of type classes to provide type safety for the persistence API. I've done a technical writeup on this work in my previous blog post. It's actually pretty interesting because I've never seen type classes quite this way before, so I think it's worth the read. The TLDR is that the repository API is now fully type safe, and the last instance of forcing users to inherit from a longevity trait has been removed.

Here are some of the other major improvements in this release:

Simplify API by merging Repo and RepoPool

Previously, the persistence API was divided into one repository - the Repo - for every kind of persistent object you wanted to persist. Some top-level operations, such as creating schema and closing session, were put into the top-level container for these repositories - the RepoPool. I've simplified these things by merging the API into a single Repo object that handles persistence for all your types.

Having a separate repository for each persistent type is a holdover from the days when a JVM persistence layer was just a wrapper around the underlying database driver. You either get a thin layer over things like database queries, or some intermediate representation that has some aspects of database modeling, and some aspects of domain modeling, such as JPQL. As such query languages are steeped in the language of your database model, they need to remain part of the persistence layer. Because every persistent type has its own set of queries, there is a need to host these queries in a repository for that persistent type. Even in something as old-fashioned as Java/JPA, we can abstract away CRUD operations, and largely inherit them from a common superclass. But queries are a bit more complicated than that.

Longevity takes a different approach. It provides an abstraction layer at just the right point - between the database model and the domain model. Database model concepts are easily abstracted into domain model terms. For instance, a database key is a way to ensure that a domain object is unique over certain attributes, and can be retrieve quickly over those attributes. We can express concepts like keys and queries without having to blur or leak between layers of abstraction, and talk about keys and queries in pure domain model terms. Once your queries become part of the domain model, the need for having a separate repository for each persistent type disappears.

Start Signing Release Tags

I started following some of the useful advice by Daniel Spiewak about how to use encryption as an open-source maintainer. I was already signing my release jars, but Daniel showed me how to sign my release tags as well. Here is a screenshot of my first signed Git tag:

(I've really got to update my icon from the Lorax at some point. It's been on GitHub and elsewhere for years. I'll always continue to speak for the trees, but that is not particularly relevant to the Scala community.)

I've also set up an account on Keybase, as Daniel suggested.

Start Using sbt-site and sbt-ghpages for Website Generation

I've improved my website documentation process a good deal with the use of two excellent SBT plugins: sbt-site and sbt-ghpages. If you use GitHub Pages to build your website or documentation, I highly recommend you check these out.

I initially set out to use tut as a tool to check that the code examples in my manual actually compiled. I wanted to replace a clumsy manual process of maintaining a copy of these code examples in my unit test suite, and copy/pasting or hand modifying to keep them in sync. It was a pretty ugly process; both time consuming and error prone. Unfortunately, tut didn't work out for me, as it is based on the Scala REPL, and the great majority of my code examples do not work in the Scala REPL without going into :paste mode (which tut doesn't currently support.) I did take some time to streamline the process a little bit, making the mapping between the code samples in my manual and their versions in the unit test suite much more direct.

What's Next

Next step to tackle is another API improvement. I want to fix the longevity API to not specialize on scala.concurrent.Future, but instead use a tagless final approach to support whatever kind of effect you like, be it Future or something more functional like an IO monad for cats. I'm looking to grow as a functional programmer, and to grow longevity as a functional library, and I decided the best place to start was the user-facing API. This 0.23 release and the tagless final stuff are the main thrusts there. I do want to keep the longevity API completely accessible to, say, recent expats from Java. My feeling with the type class work in this release is that, while it does complicate the method signatures in the Scaladocs, the user code looks more or less exactly the same. So the barrier of entry raises slightly for the Java expat in reading the API docs, but I am hoping that is offset by having a clear and complete user manual that explains what is going on.

No comments:

Post a Comment