longevity

A Persistence Framework for Scala and NoSQL

View project on GitHub

polymorphic components

Let’s say that on our blogging site, we want to be able to verify the identity of our users, and we have a few different approaches for doing so. We can use email verification, where we send the user an email, with a link that they can click to verify the email address. We can also use SMS verification, where we send the user an SMS message with a temporary code in it, and ask the user to enter it into the webpage. Or we can use a third-party account verification system such as Google Sign-In.

An idealized representation of this portion of our domain might look like so:

import org.joda.time.DateTime

sealed trait UserVerification {
  val verificationDate: DateTime
}

case class EmailVerification(
  email: Email,
  verificationDate: DateTime)
extends UserVerification

case class SmsVerification(
  phoneNumber: PhoneNumber,
  verificationDate: DateTime)
extends UserVerification

case class GoogleSignIn(
  email: Email,
  idToken: String,
  verificationDate: DateTime)
extends UserVerification

In our User object, we want to keep track of all the user’s successful verification attempts:

case class User(
  username: String,
  email: Email,
  verifications: List[UserVerification])

For this to work, all we need to do is to make longevity aware of our polymorphic component UserVerification, and its children. We do this using annotations @polyComponent and @derivedComponent:

import longevity.model.annotations.polyComponent
import longevity.model.annotations.derivedComponent
import longevity.model.annotations.persistent
import org.joda.time.DateTime

@polyComponent[DomainModel]
sealed trait UserVerification {
  val verificationDate: DateTime
}

@derivedComponent[DomainModel, UserVerification]
case class EmailVerification(
  email: Email,
  verificationDate: DateTime)
extends UserVerification

@derivedComponent[DomainModel, UserVerification]
case class SmsVerification(
  phoneNumber: PhoneNumber,
  verificationDate: DateTime)
extends UserVerification

@derivedComponent[DomainModel, UserVerification]
case class GoogleSignIn(
  email: Email,
  idToken: String,
  verificationDate: DateTime)
extends UserVerification

@persistent[DomainModel]
case class User(
  username: String,
  email: Email,
  verifications: List[UserVerification])

Note that to satisfy the requirements for shapeless, we must form a proper abstract data type by sealing the UserVerification trait. This will require us to define all the subclasses in the same file. If we forget to seal the trait, you will get an implicit resolution compiler error such as:

could not find implicit value for parameter arbitrary: org.scalacheck.Arbitrary[UserVerification]

The non-annotation equivalent is as follows:

import longevity.model.DerivedCType
import longevity.model.PType
import longevity.model.PolyCType
import org.joda.time.DateTime

sealed trait UserVerification {
  val verificationDate: DateTime
}

object UserVerification extends PolyCType[DomainModel, UserVerification]

case class EmailVerification(
  email: Email,
  verificationDate: DateTime)
extends UserVerification

object EmailVerification extends DerivedCType[DomainModel, EmailVerification, UserVerification]

case class SmsVerification(
  phoneNumber: PhoneNumber,
  verificationDate: DateTime)
extends UserVerification

object SmsVerification extends DerivedCType[DomainModel, SmsVerification, UserVerification]

case class GoogleSignIn(
  email: Email,
  idToken: String,
  verificationDate: DateTime)
extends UserVerification

object GoogleSignIn extends DerivedCType[DomainModel, GoogleSignIn, UserVerification]

case class User(
  username: String,
  email: Email,
  verifications: List[UserVerification])

object User extends PType[DomainModel, User] {
  object props {
    // ...
  }
}
prev: subtype polymorphism
up: subtype polymorphism
next: polymorphic persistents