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:
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