Alternative approach to migrations

Suggestion : store migration unique id instead of migration full class name in migration table reminded me about me thinking about migrations approaches overall:

  1. “Classic” rails approach with plain classes named starting with timestamps. Migrations are sorted by name then applied one by one.
  2. Namespaced version of 1.. Proved to be overkill. Creating more problems than it solves.
  3. Git-like approach with explicit dependencies. Not sure it even worth implementing but since it’s still in my mind, I’d like to share it.

Migration class would look like the following:

class MyMigration extends Migration
{
     ...

     public static function parent(): string
     {
         return ParentMigration::class;
     }
}

parent links current migration to migration it depends on. The method could be generated when doing migrate create based on the current state.

So migrations would be like the following:

migrations

p1 was created in a branch that was started from m2 state, n1 was created in a branch started from m3.

When doing migrate up we have to check all migration files and create a flat structure based on it. For example, we’re in the state m1 and want to migrate. Then the sequence could be:

m2
p1
m3
n1
n2
n3
m4

Potentially such approach may result in less conflict situations with extensions but it, for sure, complicates overall mechanism with graph resolving.

1 Like

namespace Yii\Modules\Post\Migrations;

class CreatePostTable extends Migration
{
     ...
     public $depends = [ 
         Yii\Modules\Auth\Migrations\CreateUserTable::class,
         Yii\Modules\Post\MigrationsCreatePostCategoryTable:class,
     ];
     public $reversedepends = [ 
         Yii\Modules\Auth\Migrations\CteatePostTagTable::class,
     ];
     public function name(){
         return 'CreatePostTable';
     }
     public function alternativesTo(){
         return [
           'appMigrations' => [
              app\migrations\m_1559573353CreatePostTableMigration::class,
              app\migrations\m_1559573353PostTableAlterMigration::class,
              app\migrations\m_1559573366PostTableAlterMigration::class,
           ],
           yii2\modules\post\m_1559573353CreatePostTableMigration::class,
        ];
    }
   ***
}

  • Run Migrations in dependency order
  • if alternative migrations exist and performed
    • Complete alternate migration set migrations if exist pending migrations in set (like appMigrations). skip current class migration
    • Skip Current migration if migration set completed
  • Otherwise perform this migrations
  • Queue Reverse dependency Migrations for executing after this
  • No Modifications in old migrations
  • Only change in migration execution logic
  • Can move migration files from one namespace to another. by just add older migration class to alternativesTo()

How would you maintain multiple depends? Manually?

For the sake of simplicity I’d go with the getVersion() approach mentioned here: https://github.com/yiisoft/migration/issues/12#issuecomment-316784109

Additionally, how about creating a unique ID (eg. UUID) getId() for migrations so they can easily be identified eg. for replacement.

This definitely sounds like an overkill. It could be useful in CMS with some plugins system, but for most apps you have linear history and defining dependencies graph for migrations looks like unnecessary complication.

If project team is large enough, history tends to be non-linear because of branching and parallel feature development.

2 Likes

From branch perspective migration dependencies are always linear. You never have case when some migration depends on migration created in the future - migration either already exists in this particular branch or it is irrelevant.

This feature will be only useful for reverting with non-linear history - you can ensure that migration will not be reverted if other migrations depends on it. But for regular apps usually it is easier to not rely on ./yii migrate/down, and just create another migration which will perform revert operations in up() - you will get simple linear migrations history and straight upgrade path.

3 Likes

Indeed. Seems it doesn’t make sense in majority of cases.

1 Like

If one module tables depend to other module tables, then timestamp simple solve correct sequentially execution of migration.

3 Likes

yes Manually, What is the problem?
May use timestamp prefix.

Doing it manually is a problem if alternative is not doing it.

Manually!

  • first required good instruction
  • second - another should take the time to read this instructions

I vote for the first option mentioned in the opening post.

I don’t see why migrations should be over-complicated. I think the problem that explicit dependencies would solve is something that should be solved in the workflow of application development. Before a branch is merged, the developer that worked on that branch should have the responsibility to check whether new migrations were added in the meantime (while merging back these changes). If necessary, he can simply rename the migration and be done with it.

2 Likes

In the case of doubt, simplicity is to be considered a virtue.

4 Likes

Exactly. When in doubt, re-read https://github.com/yiisoft/docs/blob/master/001-yii-values.md :slight_smile: That’s what I’m doing all the time.

Option 1 suffices for most of use cases & simplicity.
Does that differ from Yii2 approach … ?

In my experience, down migrations are often not backward compatible with reverse up migrations. Often I would have to write the down migration as a part of next “up” migration (e.g. do few extra database checks).

No, it’s not different from what was in Yii 1 and in Yii 2 initially. Then we’ve added namespaces.