If some changes impact the configuration, they can be managed through a migration handler at the component level (enabling trans-model migration support).
The @Version
annotation supports a migrationHandler
method which migrates the incoming configuration to the current model.
For example, if the filepath
configuration entry from v1 changed to location
in v2, you can remap the value in your MigrationHandler
implementation.
A best practice is to split migrations into services that you can inject in the migration handler (through constructor) rather than managing all migrations directly in the handler. For example:
// full component code structure skipped for brievity, kept only migration part
@Version(value = 3, migrationHandler = MyComponent.Migrations.class)
public class MyComponent {
// the component code...
private interface VersionConfigurationHandler {
Map<String, String> migrate(Map<String, String> incomingData);
}
public static class Migrations {
private final List<VersionConfigurationHandler> handlers;
// VersionConfigurationHandler implementations are decorated with @Service
public Migrations(final List<VersionConfigurationHandler> migrations) {
this.handlers = migrations;
this.handlers.sort(/*some custom logic*/);
}
@Override
public Map<String, String> migrate(int incomingVersion, Map<String, String> incomingData) {
Map<String, String> out = incomingData;
for (MigrationHandler handler : handlers) {
out = handler.migrate(out);
}
}
}
}
What is important to notice in this snippet is the fact that you can organize your migrations the way that best fits your component.
If you need to apply migrations in a specific order, make sure that they are sorted.
Consider this API as a migration callback rather than a migration API. Adjust the migration code structure you need behind the MigrationHandler , based on your component requirements, using service injection.
|
Difference between migrating a component configuration and a nested configuration
A nested configuration always migrates itself with any root prefix, whereas a component configuration always roots the full configuration.
For example, if your model is the following:
@Version
// ...
public class MyComponent implements Serializable {
public MyComponent(@Option("configuration") final MyConfig config) {
// ...
}
// ...
}
@DataStore
public class MyConfig implements Serializable {
@Option
private MyDataStore datastore;
}
@Version
@DataStore
public class MyDataStore implements Serializable {
@Option
private String url;
}
Then the component will see the path configuration.datastore.url
for the datastore url whereas the datastore
will see the path url
for the same property. You can see it as configuration types - @DataStore
, @DataSet
- being
configured with an empty root path.