Skip to content

Commit

Permalink
Add migration script (#1555)
Browse files Browse the repository at this point in the history
* [ECR-4408] Add migration script
  • Loading branch information
bullet-tooth committed May 22, 2020
1 parent 7c0260f commit cf37dce
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
@@ -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);

}
@@ -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;

0 comments on commit cf37dce

Please sign in to comment.