Handling component version migration

Talend Component Kit provides a migration mechanism between two versions of a component to let you ensure backward compatibility.

For example, a new version of a component may have some new options that need to be remapped, set with a default value in the older versions, or disabled.

This tutorial shows how to create a migration handler for a component that needs to be upgraded from a version 1 to a version 2. The upgrade to the newer version includes adding new options to the component.

This tutorial assumes that you know the basics about component development and are familiar with component project generation and implementation.

Requirements

To follow this tutorial, you need:

  • Java 8

  • A Talend component development environment using Talend Component Kit. Refer to this document.

  • Have generated a project containing a simple processor component using the Talend Component Kit Starter.

Creating the version 1 of the component

First, create a simple processor component configured as follows:

  1. Create a simple configuration class that represents a basic authentication and that can be used in any component requiring this kind of authentication.

    @GridLayout({
            @GridLayout.Row({ "username", "password" })
    })
    public class BasicAuth {
    
        @Option
        @Documentation("username to authenticate")
        private String username;
    
        @Option
        @Credential
        @Documentation("user password")
        private String password;
    }
  1. Create a simple output component that uses the configuration defined earlier. The component configuration is injected into the component constructor.

    @Version(1)
    @Icon(Icon.IconType.DEFAULT)
    @Processor(name = "MyOutput")
    @Documentation("A simple output component")
    public class MyOutput implements Serializable {
    
        private final BasicAuth configuration;
    
        public MyOutput(@Option("configuration") final BasicAuth configuration) {
            this.configuration = configuration;
        }
    
        @ElementListener
        public void onNext(@Input final JsonObject record) {
        }
    }
    The version of the configuration class corresponds to the component version.

By configuring these two classes, the first version of the component is ready to use a simple authentication mechanism.

Now, assuming that the component needs to support a new authentication mode following a new requirement, the next steps are:

  • Creating a version 2 of the component that supports the new authentication mode.

  • Handling migration from the first version to the new version.

Creating the version 2 of the component

The second version of the component needs to support a new authentication method and let the user choose the authentication mode he wants to use using a dropdown list.

  1. Add an Oauth2 authentication mode to the component in addition to the basic mode. For example:

    @GridLayout({
            @GridLayout.Row({ "clientId", "clientSecret" })
    })
    public class Oauth2 {
    
        @Option
        @Documentation("client id to authenticate")
        private String clientId;
    
        @Option
        @Credential
        @Documentation("client secret token")
        private String clientSecret;
    }

    The options of the new authentication mode are now defined.

  1. Wrap the configuration created above in a global configuration with the basic authentication mode and add an enumeration to let the user choose the mode to use. For example, create an AuthenticationConfiguration class as follows:

    @GridLayout({
            @GridLayout.Row({ "authenticationMode" }),
            @GridLayout.Row({ "basic" }),
            @GridLayout.Row({ "oauth2" })
    })
    public class AuthenticationConfiguration {
    
        @Option
        @Documentation("the authentication mode")
        private AuthMode authenticationMode = AuthMode.Oauth2; // we set the default value to the new mode
    
        @Option
        @ActiveIf(target = "authenticationMode", value = {"Basic"})
        @Documentation("basic authentication")
        private BasicAuth basic;
    
        @Option
        @ActiveIf(target = "authenticationMode", value = {"Oauth2"})
        @Documentation("oauth2 authentication")
        private Oauth2 oauth2;
    
    
        /**
        * This enum holds the authentication mode supported by this configuration
        */
        public enum AuthMode {
            Basic,
            Oauth2;
        }
    }
    Using the @ActiveIf annotation allows to activate the authentication type according to the selected authentication mode.
  1. Edit the component to use the new configuration that supports an additional authentication mode. Also upgrade the component version from 1 to 2 as its configuration has changed.

    @Version(2) // upgrade the component version
    @Icon(Icon.IconType.DEFAULT)
    @Processor(name = "MyOutput")
    @Documentation("A simple output component")
    public class MyOutput implements Serializable {
    
        private final AuthenticationConfiguration configuration; // use the new configuration
    
        public MyOutput(@Option("configuration") final AuthenticationConfiguration configuration) {
            this.configuration = configuration;
        }
    
        @ElementListener
        public void onNext(@Input final JsonObject record) {
        }
    }

The component now supports two authentication modes in its version 2. Once the new version is ready, you can implement the migration handler that will take care of adapting the old configuration to the new one.

Handling the migration from the version 1 to the version 2

What can happen if an old configuration is passed to the new component version?

It simply fails, as the version 2 does not recognize the old version anymore. For that reason, a migration handler that adapts the old configuration to the new one is required. It can be achieved by defining a migration handler class in the @Version annotation of the component class.

An old configuration may already be persisted by an application that integrates the version 1 of the component (Studio or web application).

Declaring the migration handler

  1. Add a migration handler class to the component version.

    @Version(value = 2, migrationHandler = MyOutputMigrationHandler.class)
  1. Create the migration handler class MyOutputMigrationHandler.

    public class MyOutputMigrationHandler implements MigrationHandler{ (1)
    
           @Override
           public Map<String, String> migrate(final int incomingVersion, final Map<String, String> incomingData) { (2)
               // Here we will implement our migration logic to adapt the version 1 of the component to the version 2
               return incomingData;
           }
    }
    1 The migration handler class needs to implement the MigrationHandler interface.
    2 The MigrationHandler interface specifies the migrate method. This method references:
    • the incoming version, which is the version of the configuration that we are migrating from

    • a map (key, value) of the configuration, where the key is the configuration path and the value is the value of the configuration.

Implementing the migration handler

You need to be familiar with the component configuration path construction to better understand this part. Refer to Defining component layout and configuration.

As a reminder, the following changes were made since the version 1 of the component:

  • The configuration BasicAuth from the version 1 is not the root configuration anymore, as it is under AuthenticationConfiguration.

  • AuthenticationConfiguration is the new root configuration.

  • The component supports a new authentication mode (Oauth2) which is the default mode in the version 2 of the component.

To migrate the old component version to the new version and to keep backward compatibility, you need to:

  • Remap the old configuration to the new one.

  • Give the adequate default values to some options.

In the case of this scenario, it means making all configurations based on the version 1 of the component have the authenticationMode set to basic by default and remapping the old basic authentication configuration to the new one.

public class MyOutputMigrationHandler implements MigrationHandler{

       @Override
       public Map<String, String> migrate(final int incomingVersion, final Map<String, String> incomingData) {
           if(incomingVersion == 1){ (1)
               // remapping the old configuration (2)
               String userName = incomingData.get("configuration.username");
               String password = incomingData.get("configuration.password");
               incomingData.put("configuration.basic.username", userName);
               incomingData.put("configuration.basic.password", password);

               // setting default value for authenticationMode to Basic (3)
               incomingData.put("configuration.authenticationMode", "Basic");
           }

           return incomingData; (4)
       }
}
1 Safety check of the incoming data version to make sure to only apply the migration logic to the version 1.
2 Mapping the old configuration to the new version structure. As the BasicAuth is now under the root configuration class, its path changes and becomes configuration.basic.*.
3 Setting a new default value to the authenticationMode as it needs to be set to Basic for configuration coming from version 1.
4 Returning the new configuration data.
if a configuration has been renamed between 2 component versions, you can get the old configuration option from the configuration map by using its old path and set its value using its new path.

You can now upgrade your component without losing backward compatibility.

Scroll to top