vendredi 20 mai 2016

Issues with collection of Dynamically typed service classes & objects

We are building data structure migration system that should be capable of migrating from any version of a case class to the latest one; as well as from the latest version to any one of the previous ones. The concept is as follows:

We have a structure of case classes and migration classes

v1/SchemaV1 <= Case class V1
v2/SchemaV2 <= Case class V2
v2/MigrationV2 <= Migration Class from 1->2 and back
v3/SchemaV3 <= Case class V3
v3/MigrationV3 <= Migration Class from 2->3 and back
...

Where:

  • case classes can potentially be anything, however they are implementing the same interface Schema
  • Migration classes implement Migration trait and must implement 'up' and 'down' methods that simply convert one object into another.

Currently Migration trait looks like this:

trait TypedMigration[F <: Schema, T <: Schema] {

  type FromSchema = F
  type ToSchema = T

  /**
    * This method is responsible for migrating one version of a schema 'F'(From) to another 'T' (To)
    *
    * @param from
    * @return
    */
  def up(from: FromSchema): ToSchema

  /**
    * This method is responsible for migrating one version of a schema down one level
    *
    * @param from
    * @return
    */
  def down(from: ToSchema): FromSchema
}

The idea here, that every implementation of migration class will have from and to types enforced.

As an example:

class V2Migration extends TypedMigration[v1.SchemaV1, v2.SchemaV2] {
   ...
}

How, there is a service class the responsible for managing this whole migration process. This class has two methods that handles migrations 'up' & 'down' respectively

As an example:

protected[versioning] def doMigrateUp(schema: Schema): ChecklistSchema = {

    // load all required migration classes
    val migrations = loadMigrationObjects[Schema, Schema](resolveSchemaVersion(schema), targetVersion)

    var migratedSchema = schema
    // apply all migrations up
    migrations.foreach { migration =>
      migratedSchema = migration.up(migratedSchema)
    }

    migratedSchema.asInstanceOf[ChecklistSchema]
  }

Where:

  • `loadMigrationObjects' method returns a list of migration objects in order
  • ChecklistSchema is a type alias to the latest version. So I can safely cast to it at the end.

All good till now...

The problem for me begins in the 'doMigrateDown` method:

protected[versioning] def doMigrateDown[S <: Schema](schema: ChecklistSchema, targetVersion: Int): S = {
    val migrations = loadMigrationObjects[Schema, ChecklistSchema](resolveSchemaVersion(schema), targetVersion)

    var migratedSchema = schema
    // apply all migrations down
    migrations.foreach { migration =>
      migratedSchema = migration.down(migratedSchema)
    }

    migratedSchema.asInstanceOf[S]
  }

Here, there are two problems:

  1. I am getting compilation error: enter image description here

  2. I am not sure if this casting can actually work.

I this point I am totally stuck...

For the reference, this is how we build the list of migration objects:

protected[versioning] def loadMigrationObjects[F <: Schema, T <: Schema](currentVersion: Int, targetVersion: Int): List[TypedMigration[F, T]] = {

    val migrationsRange = if (currentVersion <= targetVersion) {
      currentVersion + 1 to targetVersion
    } else {
      currentVersion to targetVersion + 1 by -1
    }

    val migrations = List[TypedMigration[F, T]]()

    migrationsRange.foldLeft(migrations) { case (acc, versionNumber) =>
      Try {

        val migrationClassName = MigrationClassNameFormat.format(versionNumber, versionNumber)
        Class.forName(migrationClassName).newInstance().asInstanceOf[TypedMigration[F, T]]

      } match {
        case Success(m) => acc :+ m
        case Failure(e: ClassNotFoundException) =>
          logger.info("migration class was not found", e)
          acc
        case Failure(e) => throw e
      }
    }

  }





Aucun commentaire:

Enregistrer un commentaire