Duplicate Model code in complex data migrations
Problem description
Per our guidelines, we can not rely on existing models while working on a data migration. Migration code must be isolated and can not use application code (e.g. models defined in app/models).
Background migrations must be isolated and can not use application code (e.g. models defined in app/models). Since these migrations can take a long time to run it’s possible for new versions to be deployed while they are still running.
This is true for background migrations, but we also follow that rule for all data migrations, including post-deployment ones.
That means that migration authors have to replicate existing functionality on the migration class.
For most cases this is not an issue, but if multiple core models are required (like projects, issues, or namespaces), a lot of functionality must be replicated.
As we can not efficiently replicate thousands of lines of thoroughly curated Model code, we rely on the migration author to cherry pick the appropriate parts of the models or replicate in a more simple way the existing Model functionality.
The aforementioned process is prone to errors, especially with various constraints not always enforced on the database layer or clearly defined on the Model layer.
As an example, we require that paths are unique, but we do not enforce that on the database layer for Namespaces or the Namespace Model. That is indirectly enforced by the code that generates the path when a Routable model is created.
We require specs to be written for all data migrations, but bugs can slip through those tests as they are not as exhaustive as the ones we have for each of our Models.
As is the case with replicating all the related Model code, the same holds for the Model specs. We could require that extensive tests are written, but once more, there are thousands of lines of specs for core Models like Project, Issue or Namespace. We can not require that all those tests are fully replicated, but at the same time skipping them means that business logic errors can hide inside a data Migration.
That becomes even worse with some data migrations directly updating the database without passing through a model.
Possible solutions
I am not sure if there is a solution that can catch all edge cases without requiring thousands of lines of code to be replicated.
We can require more extensive reviews of database migrations, but this is not the solution either.
If there was a solution to auto-magically freeze somehow the existing Model code when a migration is shipped and use that, it would guarantee that both (i) a well tested version of the application code is used and (ii) that the migration is not affected by new code updates.
Other better solutions may exist and this issue's purpose is to discuss such alternatives and how we could approach this problem.