persisting polymorphism
When we use polymorphic persistent objects in our domain, we end up with
a repository that performs operations on the parent type, as well as on all of the derived
subtypes. In this example, we have a User trait, and two inheriting subclasses, Member and
Commenter:
import longevity.model.annotations.derivedPersistent
import longevity.model.annotations.polyPersistent
@polyPersistent[DomainModel]
trait User {
val username: Username
}
object User {
implicit val usernameKey = key(props.username)
}
@derivedPersistent[DomainModel, User]
case class Member(
username: Username,
email: Email,
numCats: Int)
extends User
object Member {
implicit val emailKey = key(props.email)
}
@derivedPersistent[DomainModel, User]
case class Commenter(
username: Username)
extends User
Note that User and Member each have their own key, on username and email, respectively.
When we construct our longevity context, we get a repository that can perform
persistence operations on both persistent classes. For example, we can create Users, Members,
and Commenters:
import longevity.context.LongevityContext
import longevity.persistence.Repo
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
val context = LongevityContext[Future, DomainModel]()
val repo: Repo[Future, DomainModel] = context.repo
val user: User = Member(Username("u1"), Email("e1"), 3)
val member: Member = Member(Username("u2"), Email("e2"), 5)
val commenter: Commenter = Commenter(Username("u3"))
import longevity.persistence.PState
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val fpUser: Future[PState[User]] = repo.create(user)
val fpMember: Future[PState[Member]] = repo.create(member)
val fpCommenter: Future[PState[Commenter]] = repo.create(commenter)
We can only retrieve based on the type that actually contains the key:
val retrievedUser: Future[Option[PState[User]]] =
repo.retrieve[User](commenter.username)
val retrievedMember: Future[Option[PState[Member]]] =
repo.retrieve[Member](member.email)
Trying repo.retrieve[Commenter](commenter.username) would produce a compiler error, because
Commenter does not have a key on username.
PStates are invariant in their Persistent type parameter, so the PStates can not be used
interchangeably. Keys, KeyVals, and Queries are also all invariant in their type parameter, so
in general, you want to be working with one Persistent type at a time.
That said, there is some flexibility added on to work around these
invariant type parameters. For example, a PState[Member] instance is
not also a PState[User]. But we can safely convert a
PState[Member] into a PState[User], using method widen:
val memberState: PState[Member] = getMemberState()
val userState: PState[User] = memberState.widen[User]
And while a Query[Member] is not also a Query[User], you can use
User properties when constructing your Query[Member]:
import longevity.model.query.Query
import Member.queryDsl._
val query: Query[Member] =
User.props.username eqs Username("u7") and
Member.props.numCats gt 2
val queryResults = repo.queryToIterator(query)