-
Notifications
You must be signed in to change notification settings - Fork 6
Implementation Approach for Poly and Derived Repos
How longevity models subtype polymorphism is described in the user manual here. A description of how the repository handles polymorphism is described in the user manual here.
A quick note here before getting started. The current setup has the unfortunate effect of creating a cycle between Repo
and PRepo
. I was just looking in to how to remove this, and I see that any solution I put in place now will largely be deleted when I get around to replacing emblem with shapeless. So I am going to put removing that cycle on hold for now.
Using PolyPType
and DerivedPType
variants of PType
, the user is able to store persistent objects with different types in the same backing table/collection. The user is provided with Repos
for their PolyPType
, as well as all of the DerivedPTypes
, so we have a situation where multiple repositories are using a single table. This is a little bit tricky to coordinate, so in this essay, we breakdown how this works in the code.
- the poly repo is responsible for creating and maintaining the table/collection, as well as any keys and indexes found in the
PolyPType
. - the derived repo is responsible for creating an maintaining the keys and indexes found in the
DerivedPType
. - creates and updates received by the poly repo are forwarded on to the appropriate derived repo. this is done because the poly repo is not aware of any maintenance needed for the
DerivedPType
's keys and indexes. - derived repo retrieval methods must all filter on the discriminator.
- derived repo gets a handle on the poly repo, and not vice versa.
That's the basic idea, but let's break that down into a little more detail. As of this writing, the three back ends (all quite different in implementation) are InMem, Mongo, and Cassandra.
- schema creation:
- poly repo:
- constructs the table/collection, plus support for poly keys and indexes. (this is the same behavior as the corresponding vanilla repo.)
- also creates the discriminator column. this is only necessary for Cassandra
- derived repo:
- constructs support for derived keys and indexes.
- note that in order to do this the derived repo needs to get a handle on the table/collection from the poly repo.
- all derived indexes should have 'discriminator' as initial column, as every query from the derived repo will also be filtering on the discriminator. for cassandra this doesnt matter, since cassandra indexes are all single-column
- note for
inmem
, this means updatingkeyValToEntityMap
with derived keys on create/update
- constructs support for derived keys and indexes.
- poly repo:
- create:
- poly:
- identifies the derived type, delegates to the derived repo
- note this behavior is shared across all back ends in
BasePolyRepo
- note this behavior is shared across all back ends in
- identifies the derived type, delegates to the derived repo
- derived:
- same as super, but adds discriminator values
- for cassandra, sets any realized props from both poly and derived
- for inmem, logs all
KeyVals
from both poly and derived
- poly:
- retrieve KeyVal
- poly:
- same as super
- derived:
- same as super, but:
- uses the poly collection instead of its own collection
- filters on the discriminator as well
- this is not necessary for inmem since the key vals are only put in by the appropriate derived repo
- same as super, but:
- poly:
- retrieve Query
- poly:
- same as super
- derived:
- same as super, but:
- uses the poly collection instead of its own collection
- filters on the discriminator as well
- same as super, but:
- poly:
- update
- poly:
- identifies the derived type, delegates to the derived repo
- NOTE this behavior is shared across all back ends in
BasePolyRepo
- NOTE this behavior is shared across all back ends in
- identifies the derived type, delegates to the derived repo
- derived:
- same as super, but adds discriminator values
- for cassandra, sets any realized props from both poly and derived
- poly:
- delete
- poly:
- same as super
- derived:
- same as super, but:
- uses the poly collection instead of its own collection
- optionally filters on the discriminator as well
- NOTE in cassandra not able to do this due to "Non PRIMARY KEY discriminator found in where clause"
- same as super, but:
- poly:
one drawback of this approach is that it fails in the face of updates that change the persistent object from one derived type to another. this is a problem in inmem because we try to remove KeyVals
based on the orig
instance in the PState
, and the types don't match up here. so Key.keyValForP
ends up throwing a reflection exception. (this could be avoided splitting out an "update cleanup" operation, and having the poly repo delegate update cleanup to the orig derived repo, and the actual update to the current derived repo.) it is a potential problem for Cassandra because it will fail to overwrite realized property columns for the original derived type. this will not presently trip up longevity, since we are always filtering on the discriminator column. but it would look bad to users examining the database directly.
ive handled this drawback situation by simply disallowing the user from morphing a Derived1
into a Derived2
within a PState[Poly]
, and modifying the RepoCrudSpec
to not exhibit this kind of behavior.