-
Notifications
You must be signed in to change notification settings - Fork 411
[FEATURE] Setup: add database update steps guideline. #11616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,197 @@ | ||||||
| # Database Update Steps Guideline | ||||||
|
|
||||||
| This document defines how database update steps **SHOULD** be implemented, so the database in kept in a consistent state | ||||||
| accross multiple major releases and different branches of the repository. It serves as a reference for [authorities who | ||||||
| sign off on code changes](../../../../docs/development/maintenance.md#authorities) and guides all ILIAS developers who | ||||||
| perform database updates. | ||||||
|
|
||||||
| This guide will refer to database updates as schmea updates (DDL), but it possibly applies to other operations (DML, | ||||||
| DQL, DCL) as well. Please note that we have [migrations](../README.md#on-migration) for more complex DML operations. | ||||||
|
|
||||||
| ## Table of Contents | ||||||
|
|
||||||
| 1. [How ILIAS Executes Database Updates](#1-how-ilias-executes-database-updates) | ||||||
| 2. [Why Version-Namespaced Classes Are Recommended](#2-why-version-namespaces-are-recommended) | ||||||
| 3. [Best Practices & Examples](#3-best-practices--examples) | ||||||
|
|
||||||
| ## 1. How ILIAS Executes Database Updates | ||||||
|
|
||||||
| We use the [ILIAS Setup component](../README.md) to execute database updates. Other (ILIAS) components can provide an | ||||||
| implementation of the `\ilDatabaseUpdateSteps` interface, which is a collection of sequential database update | ||||||
| steps, that is achieved using the Setup's `\ilDatabaseUpdateStepsExecutedObjective` objective provided by their agent. | ||||||
|
|
||||||
| The Setup component tracks which steps have been executed, so updates are not performed more than once. This is done | ||||||
| using a combination of: | ||||||
|
|
||||||
| - the step number (gathered from your `step_<nr>()` methods) | ||||||
| - the FQDN of your implementation (class that implements `\ilDatabaseUpdateSteps`) | ||||||
|
|
||||||
| **It is therecore important that the FQDN of an implementation MUST NOT change.** Otherwise update steps are considered | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| new and are potentially executed more than once. | ||||||
|
|
||||||
| ### 1.1 Database Update Steps Lifespan | ||||||
|
|
||||||
| Every `\ilDatabaseUpdateSteps` implementation has a finite lifespan, which is determined by when it was introduced and | ||||||
| how ILIAS installations move through different major versions. | ||||||
|
|
||||||
| ILIAS does not allow you to skip a major version during an upgrade (e.g. jumping directly from 8 to 10). However, minor | ||||||
| versions within a major version **COULD** be skipped. Since minor versions are also released after the next major | ||||||
| version is already published, this cannot be enforced properly. This means an installation which upgrades to a new major | ||||||
| version must also execute any update steps which have been skipped within the previous major version. | ||||||
|
|
||||||
| Now it becomes clear, that the next major versions **MUST** carry the same and possibly skipped database update steps | ||||||
| forward until the next major version is released. This guarantees that all update steps are eventually executed in the | ||||||
| appropriate order. | ||||||
|
|
||||||
| Concretely: if a skipped minor version falls under major version `n`, its update steps must still be executable when | ||||||
| upgrading to version `n+1`. To cover all such cases, every `\ilDatabaseUpdateSteps` implementation **MUST** remain in | ||||||
| the codebase until the initial release of version `n+2`, where it can be safely removed. The initial release of a major | ||||||
| version is also the point at which the [database template](../../setup/sql/ilias3.sql) for new installations is updated, | ||||||
| which is why it serves as the safe removal point. | ||||||
|
|
||||||
| ## 2. Why Version Namespaces Are Recommended | ||||||
|
|
||||||
| ILIAS supports the parallel maintenance of multiple major versions, typically two fully maintained versions, one version | ||||||
| for development, and one older version receiving only security bugfixes. This totals up to four distinct Git branches | ||||||
| which are maintained at some point in the ILIAS lifecycle. | ||||||
|
|
||||||
| If an implementation of `\ilDatabaseUpdateSteps` is updated in an older version but development has already continued | ||||||
| in a newer version of ILIAS, then database inconsistencies might be introduced due to divergent `step_<x>()` methods. In | ||||||
| other words, when a new `step_<x>()` method is added to an implementation on one branch, the same implementation on | ||||||
| another branch **MUST NOT** implement the same method for a different purpose. | ||||||
|
|
||||||
| The safest way to guarantee this is to **give each major version its own class**, so their FQDN never overlap. This way | ||||||
| e.g. `ILIAS\ComponentX\Setup\Database\V10\UpdateSteps::step_2()` is completely independent from | ||||||
| `ILIAS\ComponentX\Setup\Database\V11\UpdateSteps::step_2()`. | ||||||
|
|
||||||
| ### 2.2. Recommended Namespace Pattern | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has to be 2.1, not 2.2. |
||||||
|
|
||||||
| To implement this consistently accross all ILIAS components and prevent possibly divergent database update steps, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| developers **SHOULD** follow this namespace pattern: | ||||||
|
|
||||||
| ``` | ||||||
| <Vendor>\<Component>\<PathToUpdateSteps>\<Version> | ||||||
| ``` | ||||||
|
|
||||||
| Whereas the placeholders are replaced like: | ||||||
|
|
||||||
| - `<Vendor>`: the provider of your component (e.g. `ILIAS`) | ||||||
| - `<Component>`: the name of your component (e.g. `Setup`, `ResourceStorage`) | ||||||
| - `<PathToUpdateSteps>`: desired path/structure to your Setup-related classes and update steps (e.g. `Setup\Database`) | ||||||
| - `<Version>`: the ILIAS major version, prefixed with capital "V" (e.g. `V10`, `V11`) | ||||||
|
|
||||||
| ## 3. Best Practices & Examples | ||||||
|
|
||||||
| ### 3.1. Introducing Database Update Steps | ||||||
|
|
||||||
| To introduce new database update steps your component **MUST** implement the `\ilDatabaseUpdateSteps` interface, which | ||||||
| **SHOULD** be namespaced as described by the previous chapter. The interface description explains how methods **MUST** | ||||||
| look like, so the Setup can find and execute them properly. | ||||||
|
|
||||||
| ```php | ||||||
| namespace ILIAS\ComponentX\Setup\Database\V10; | ||||||
|
|
||||||
| /** @since ILIAS 10 */ | ||||||
| class DatabaseUpdateStepsOfX implements \ilDatabaseUpdateSteps | ||||||
| { | ||||||
| public function step_1(): void | ||||||
| { | ||||||
| $this->db->createTable('x', [...]); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| If your database update steps are introduced to two or more supported versions, we still recommend to provide a | ||||||
| dedicated class for each major version, to guarantee other developers do not accidentally introduce divergent update | ||||||
| steps (i.e. `step_<x>()` methods). | ||||||
|
|
||||||
| To contribute your update steps to the system, your component needs to implement an `ILIAS\Setup\Agent` which returns an | ||||||
| instance of the `\ilDatabaseUpdateStepsExecutedObjective` objective that receives an instance of your | ||||||
| `\ilDatabaseUpdateSteps` implementation in `ILIAS\Setup\Agent::getInstallObjective()` or `::getUpdateObjective()`, | ||||||
| depending on your goal. Read the respective method descriptions for detailed instructions. | ||||||
|
|
||||||
| ```php | ||||||
| namespace ILIAS\ComponentX\Setup; | ||||||
|
|
||||||
| class AgenfOfX implements \ILIAS\Setup\Agent | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| { | ||||||
| // ... | ||||||
|
|
||||||
| public function getUpdateObjective(?\ILIAS\Setup\Config $config = null): \ILIAS\Setup\Objective | ||||||
| { | ||||||
| return new \ilDatabaseUpdateStepsExecutedObjective( | ||||||
| new \ILIAS\ComponentX\Setup\Database\V10\ilDatabaseUpdateStepsOfX(), | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Bonus tip: use an `ILIAS\Setup\ObjectiveCollection` if you have more than one `\ilDatabaseUpdateSteps` implementation. | ||||||
|
|
||||||
| ### 3.2. Grouping Database Update Steps | ||||||
|
|
||||||
| Besides using version namespaces, we also recommend to group database update steps strategically, rather than | ||||||
| consolidating them all in one single `\ilDatabaseUpdateSteps` implementation. This allows you to: | ||||||
|
|
||||||
| - remove your database update steps **at the end of their lifespan**, and | ||||||
| - ensure your objective(s) can be executed in a timely manner. | ||||||
|
|
||||||
| By strategically we mean that update steps **COULD** be grouped by the kind of operation, the table or -column they | ||||||
| affect, or a feature or bugfix they implement. There is no best strategy and this is primarily shaped by preference, but | ||||||
| its something to keep in mind. Smaller more and dedicated classes help others understand the goal of your database | ||||||
| updates and can even expresses what should happen to the database programatically: | ||||||
|
|
||||||
| ```php | ||||||
| namespace ILIAS\ComponentX\Setup; | ||||||
|
|
||||||
| class AgenfOfX implements \ILIAS\Setup\Agent | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| { | ||||||
| // ... | ||||||
|
|
||||||
| public function getUpdateObjective(?ILIAS\Setup\Config $config = null): \ILIAS\Setup\Objective | ||||||
| { | ||||||
| return new \ILIAS\Setup\ObjectiveCollection( | ||||||
| "Database update steps of Component X for ILIAS 10", | ||||||
| true, | ||||||
| new \ilDatabaseUpdateStepsExecutedObjective(new \ILIAS\ComponentX\Setup\Database\V10\CreateFooTable()), | ||||||
| new \ilDatabaseUpdateStepsExecutedObjective(new \ILIAS\ComponentX\Setup\Database\V10\UpdateFooBarDefaultValue()), | ||||||
| new \ilDatabaseUpdateStepsExecutedObjective(new \ILIAS\ComponentX\Setup\Database\V10\AlterFooBazMaxLength()), | ||||||
| new \ilDatabaseUpdateStepsExecutedObjective(new \ILIAS\ComponentX\Setup\Database\V10\DeleteUnusedFooEntries()), | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| The example above demonstrates how speaking and grouped database update steps can already convey much of the information | ||||||
| on a programming level – without having a look at its concrete steps. The objective communicates very clearly that | ||||||
| throughout the major version 10 a new `foo` table will be added, whose `foo.bar` default value and `foo.baz` column type | ||||||
| is updated, and some unused entries are cleaned up. | ||||||
|
|
||||||
| Bonus tip: use an `ILIAS\Setup\ObjectiveWithPreconditions` to control the order of your `\ilDatabaseUpdateSteps`. | ||||||
|
|
||||||
| ### 3.2. Removing Database Update Steps | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And again "3.2" ;-) |
||||||
|
|
||||||
| As mentioned in previous chapters, database update steps have a finite lifespan, after which they **SHOULD** be removed | ||||||
| from the code-base to prevent the accumulation of unused code and reduce the maintenance overhead. An | ||||||
| `\ilDatabaseUpdateSteps` implementation reaches EOL `n+2` major versions after its introduction. At this point the | ||||||
| upgrade strategy and the release process of ILIAS will ensure that these update steps have been executed and are | ||||||
| contained inside the database template for new installations. This makes the implementations obsolete. | ||||||
|
|
||||||
| When removing database update steps you **MUST** ensure: | ||||||
|
|
||||||
| - only entire classes are removed, never individual `step_<x>()` methods (would cause divergence), and | ||||||
| - no additional `step_<x>()` methods were added since the introduction, otherwise the `n+2` resets to the last update. | ||||||
|
|
||||||
| To verify that an `\ilDatabaseUpdateSteps` implementation was not updated since its introduction, you can run the | ||||||
| following Git command: | ||||||
|
|
||||||
| ```bash | ||||||
| SINCE="<branch>" FILE="<path>" sh -c 'git diff HEAD..$SINCE -- $FILE' | ||||||
| ``` | ||||||
|
|
||||||
| Whereas the placeholders are replaced like: | ||||||
|
|
||||||
| - `<branch>`: the branch of the major version of `n-2` (e.g. `release_10` for `trunk`, `release_9` for `release_11`) | ||||||
| - `<path>`: the path to the file you want check (e.g. `components/ILIAS/ComponentX/Setup/Database/V10/FooBar.php`) | ||||||
|
|
||||||
| If there were no new `step_<x>()` methods, the specified file can safely be removed. | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.