Run migrations in module dependency order
- Created
- Versions
- History
Context
Modules keep tables of their own, and one module's tables can refer to another's, such as an invoice referring to the server it bills. A table has to exist before anything can refer to it.
ADR-0023 records a migration design that runs in three stages, creating tables, then the references between them, then changing data, with each stage run across every migration before the next begins, so that modules can refer to each other's tables without their migrations being ordered.
Bundled modules ship inside the binary, per ADR-0009, and are not Composer packages. Only external modules declare Composer requirements, per ADR-0012. Every bundled module's migrations run when a binary is replaced, as ADR-0026 decides, whether or not the module is enabled.
The module system, in RFC-0015, resolves no dependency between two modules and orders them by none. A module is enabled by its configuration being present, so it can be enabled without any command.
Decision
We will run migrations module by module, in an order where every module comes after the modules it depends on, and drop the three stages, so that each migration runs whole. A module that refers to another module's tables will declare a module dependency on it in its manifest. The module system will provide the order, covering every installed module, enabled or not, and will refuse a dependency that is missing or circular, a bundled module depending on an external one, and removing a module another installed module depends on. An enabled module whose dependency is disabled will be skipped when the panel boots, with the reason logged, and the commands that enable and disable modules will refuse to leave a module in that state.
Alternatives
Running each stage across every module. Migrations would run in the three stages, with each stage run for every module before the next, so that modules could refer to each other's tables without being ordered. We rejected this because ordering modules by their module dependencies already lets one module's tables refer to another's, and a reference to another module's table is a dependency that should be declared.
Deriving the order from Composer. The order would come from each external module's Composer requirements. We rejected this because bundled modules are not Composer packages, so a dependency on one could not be expressed, and building an order from Composer's requirements is more complex than declaring it.
The migrations ordering modules themselves. Whatever runs migrations would build its own order from the manifests. We rejected this because a dependency between modules is a fact about modules, which enabling, disabling and removing them need as well.
Consequences
Easier:
- A migration will create a table together with its references to another module's tables, because those tables will already exist.
- Enabling, disabling and removing modules will be able to use the same declared module dependencies.
Harder:
- A module's author will have to declare every module whose tables it refers to.
- The module system will have to validate dependencies and provide an order before any migration runs.
- Nothing will check that a migration's references match its declared dependencies, so an undeclared reference may work on one installation and fail on another, depending on where the order places the two modules.
Constrained:
- Two modules will not be able to refer to each other's tables.
- A bundled module will not be able to depend on an external one.
- A module whose dependency is disabled will not run, even when it is enabled.
Source adr/0028-module-dependency-order.md · commit 072385a · exported