Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
2 changed files
with
193 additions
and
0 deletions.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
...binding/core/src/main/java/com/exonum/binding/core/service/migration/MigrationScript.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright 2020 The Exonum Team | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.exonum.binding.core.service.migration; | ||
|
||
import com.exonum.binding.core.service.ExecutionContext; | ||
import java.util.Optional; | ||
import org.pf4j.ExtensionPoint; | ||
|
||
/** | ||
* Migration script interface which allows to construct data migrations performed during service | ||
* evolution. | ||
* | ||
* <p/> | ||
* Migration script execution logic needs retain the knowledge about data types used in the service | ||
* in the past. Since these data types may be unused <b>currently</b>, retaining them may place | ||
* a burden on the service. To mitigate this, you can provide a minimum supported starting version | ||
* of the service via {@link #minSupportedVersion()}. | ||
* | ||
* <p/> | ||
* <h3>Contract and restrictions</h3> | ||
* Service artifact module could have any number of migration script implementations. To be visible | ||
* and applicable by the system those implementations must satisfy the following contract: | ||
* | ||
* <ul> | ||
* <li>Migration script class should implement {@linkplain MigrationScript this interface}.</li> | ||
* <li>Migration script class should have default constructor.</li> | ||
* <li>Migration script class should be marked with {@link org.pf4j.Extension} annotation.</li> | ||
* <li>There shouldn't be more two (or more) scripts for the same | ||
* {@linkplain #targetVersion() target version} in the module.</li> | ||
* </ul> | ||
* | ||
* @see com.exonum.binding.core.service.migration | ||
* @see <a href="https://semver.org/">Semantic versioning</a> | ||
*/ | ||
public interface MigrationScript extends ExtensionPoint { | ||
|
||
/** | ||
* Represents a name of the migration script. | ||
* I.e. short description of the migration purpose in a free form. | ||
*/ | ||
String name(); | ||
|
||
/** | ||
* Minimum version of the service data the current script compatible with. | ||
* Or {@link Optional#empty()} if the script is compatible with any data version. | ||
*/ | ||
Optional<String> minSupportedVersion(); | ||
|
||
/** | ||
* Version of the service data the current script migrates to. | ||
*/ | ||
String targetVersion(); | ||
|
||
//TODO: ECR-4413 replace ExecutionContext with MigrationContext | ||
|
||
/** | ||
* Performs data migration. | ||
* | ||
* @param context migration context | ||
*/ | ||
void execute(ExecutionContext context); | ||
|
||
} |
116 changes: 116 additions & 0 deletions
116
...va-binding/core/src/main/java/com/exonum/binding/core/service/migration/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright 2020 The Exonum Team | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/** | ||
* <h1>Migrations Overview</h1> | ||
* The goal of a data migration is to prepare data of an Exonum service for use with an updated | ||
* version of the service business logic. In this sense, migrations fulfil the same role | ||
* as migrations in traditional database management systems and have the similar mechanism: | ||
* | ||
* <ul> | ||
* <li>Migration scripts will be applied to service data in a specific order during evolution | ||
* of a particular service instance. Each script will be applied exactly once.</li> | ||
* <li>Several migration scripts may be applied sequentially if the instance is old enough.</li> | ||
* <li>Migrations for different service instances are independent, and migration scripts for them | ||
* are fully reusable.</li> | ||
* </ul> | ||
* | ||
* <p/> | ||
* Migrations are performed via {@link com.exonum.binding.core.service.migration.MigrationScript}'s. | ||
* A script takes data of a service and transforms it to a new version. | ||
* Migration is non-destructive, i.e., does not remove the old versions of migrated indexes. | ||
* Instead, new indexes are created in a separate namespace, and atomically replace the old data | ||
* when the migration is flushed. | ||
* | ||
* <p/> | ||
* Similar to other service lifecycle events, data migrations are managed by the | ||
* {@linkplain com.exonum.binding.core.runtime.ServiceRuntime runtime}. | ||
* | ||
* <p/> | ||
* <h1>Migration Workflow</h1> | ||
* Migration starts after the block with the request is committed and is performed asynchronously. | ||
* | ||
* <p/> | ||
* After the local migration completion, validator nodes report the result of migration, which can | ||
* be either successful or unsuccessful. | ||
* | ||
* <p/> | ||
* If all validators report the successful local migration result, and the resulting state hashes | ||
* match, migration is committed and flushed in the block, next to block with the last required | ||
* migration confirmation. | ||
* | ||
* <p/> | ||
* In any other case (e.g. migration failure for at least one node, resulting state hash divergence, | ||
* lack of report at the deadline height), migration is considered failed and rolled back. | ||
* | ||
* <p/> | ||
* After fixing the reason for migration failure, the migration attempt can be performed once again. | ||
* It will require a different deadline height or a different seed, since `MigrationRequest` objects | ||
* are considered unique and supervisor won't attempt to perform the same `MigrationRequest` again. | ||
* | ||
* <p/> | ||
* <h2>Complex Migrations</h2> | ||
* If a service module contains more than one migration script (e.g. if you need to migrate service | ||
* from version 0.1 to version 0.3, and this will include execution of two migration scripts: | ||
* 0.1 -> 0.2 and 0.2 -> 0.3), they will be executed sequentially, one migration script at the time. | ||
* | ||
* <p/> | ||
* <h2>Incomplete Migrations</h2> | ||
* Migrations require only the current and the last version of artifact to be deployed. If you | ||
* decide to stop migration before reaching the last version (e.g. you requested migration to | ||
* version 0.3, but decided to go with version 0.2), you will need to deploy the 0.2 artifact | ||
* in order to resume the migrated service. | ||
* | ||
* <p/> | ||
* <h1>Examples</h1> | ||
* Consider the following hypothetical evolution of a service: | ||
* | ||
* <table> | ||
* <tr> | ||
* <th>Version</th><th>Migration</th> | ||
* </tr> | ||
* <tr> | ||
* <td>0.2.0</td><td>#1: Split `name` in user accounts into `first_name` and `last_name`</td> | ||
* </tr> | ||
* <tr> | ||
* <td>0.3.0</td><td> - </td> | ||
* </tr> | ||
* <tr> | ||
* <td>0.4.0</td><td>#2: Consolidate token metadata into a single `Entry`</td> | ||
* </tr> | ||
* <tr> | ||
* <td>0.4.1</td><td> - </td> | ||
* </tr> | ||
* <tr> | ||
* <td>0.4.2</td><td> #3: Compute total number of tokens and add it to metadata</td> | ||
* </tr> | ||
* </table> | ||
* | ||
* <p/> | ||
* In this case: | ||
* <ul> | ||
* <li>If a service instance is migrated from version 0.1.0 to the newest version 0.4.2, all three | ||
* scripts need to be executed.</li> | ||
* <li>If an instance is migrated from 0.2.0 or 0.3.0, only scripts #2 and #3 need to be executed. | ||
* </li> | ||
* <li>If an instance is migrated from 0.4.0 or 0.4.1, only script #3 needs to be executed.</li> | ||
* <li>If the instance version is 0.4.2, no scripts need to be executed.</li> | ||
* </ul> | ||
* | ||
* @see <a href="https://exonum.com/doc/version/latest/architecture/services/#data-migrations"> | ||
*Exonum documentation</a> | ||
**/ | ||
package com.exonum.binding.core.service.migration; |