Skip to content

Implementation Approach for Poly and Derived Repos

John Sullivan edited this page Oct 16, 2017 · 5 revisions

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 updating keyValToEntityMap with derived keys on create/update
  • create:
    • poly:
      • identifies the derived type, delegates to the derived repo
        • note this behavior is shared across all back ends in BasePolyRepo
    • 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
  • 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
  • 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
  • update
    • poly:
      • identifies the derived type, delegates to the derived repo
        • NOTE this behavior is shared across all back ends in BasePolyRepo
    • derived:
      • same as super, but adds discriminator values
      • for cassandra, sets any realized props from both poly and derived
  • 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"

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.