Longevity Now Has a Streamlined API!

I'm very excited to announce the 0.9.0 release of longevity, which features a whole slew of API improvements that make longevity much easier to use and understand. Here are the three most significant changes:

  • Shorthands are gone
  • Assocs are gone
  • The API for Key and KeyVal has been reworked for significant ease-of-use improvements
I'll discuss each of these changes in more detail below.


First of all, shorthands. These were essentially value objects containing a single field. For instance, we might have a Username type that we use in our domain model instead of a raw string, for improved type-safety:

case class Username(username: String)

The only advantage to using shorthands over value objects was for a more compact JSON representation. Previously, if you chose to make Username a value object, you would get JSON like this for a user:

  username: {
    username: "smithy"
  email: "smithy@example.com",
  fullName: "John Smith"

If you chose to use a shorthand instead, you would get JSON like this:

  username: "smithy",
  email: "smithy@example.com",
  fullName: "John Smith"

As of the 0.9 release, all single-field embeddables are inlined in the JSON in this way, so there is no longer any advantage in using shorthands.

The end result is that shorthands no longer serve a purpose, and are now gone. This is a wonderful simplification to the API that I hope users will appreciate.


Assocs used to be a way to create relationships between two persistent objects. In traditional Domain Driven Design terminology, this would be a aggregative relationship between two entity aggregates. Aside from being misnamed (association instead of aggregation), they were strange, and largely redundant with Keys and KeyVals. I've been uncomfortable with them for a while, but I was hesitant to pull them because the API for using Keys was both cumbersome and confusing. I finally had enough while I was working on a sample application, and decided it was time to fix up the API for using Keys and KeyVals. This allowed me to remove Assocs with no loss of convenience or functionality. Once again, this is a great API simplification - the smaller the better! And I'm optimistic that people will find the new Keys API intuitive and natural to use.

KeyVals and Keys

Keys are a way to declare a specific kind of meta-information about a persistent type: that no two persistent objects should have the same value for that key. They also provide a way to lookup up objects from your persistent store. Unfortunately, the original longevity design for keys focused on the former quality, at the expense of the latter. A key was a sequence of properties of the persistent object that would together make the object unique. You could build key values for database lookup, but the API was clumsy and not very type-safe.

The biggest hurdle at this point was that I need to support compound keys - keys that contained more than one simple value. But in the past, I was only able to specify properties for simple values. So I bit the bullet and enhanced that part of the system so that users could specify values for compound properties. Now, key values are case classes, and are easily retrieved from the persistent object on either side of the aggregative relationship. Here's an example where Users have Username as a key:

case class Username(username: String)
extends KeyVal[User, Username](User.keys.username)

case class User(
  username: Username,
  firstName: String,
  lastName: String)
extends Root

object User extends RootType[User] {
  object props {
    val username = prop[Username]("username")
  object keys {
    val username = key(props.username)
  object indexes {
The contents of object User may look a little strange, but it will quickly make sense if skim that chapter of the manual. It's just declaring User.username as a key.

Let's say a BlogPost has multiple authors. In our BlogPost case class, we simply specify the authors by Username:

case class BlogPost(
  uri: BlogPostUri,
  title: String,
  content: Markdown,
  authors: List[Username])
extends Root
And we easily can retrieve the first author from the post:

val authorUsername: Username = blogPost.authors.head
val author: FPState[User] = userRepo.retrieveOne(authorUsername)
I hope you agree that this looks dead-simple and entirely intuitive.

Closing Thoughts

I can't express in words how happy I am with the new longevity API. The improvements really show in the latest version of the Getting Started Guide, which reads about ten times nicer than the previous version.

Over the past few months, longevity has really been firming up in a lot of ways. We've added some great new features, along with other API improvements in earlier releases. At this point, I feel fully confident in saying that the longevity API is solidifying, and the core API should remain stable from here on out. The user manual is in great shape, and all the critical features are in place as well. Added features from here on out are gravy! Please give it a try. Please know that I am more than ready to help out any early adopters with getting started - whether that means just answering questions and giving advice, reprioritizing future longevity features that you want or need, or even partnering with you on establishing longevity in your project infrastructure.

Okay, back to working on my Lightbend Activator tutorial! 

No comments:

Post a Comment