Versioning through Incremental Updates

Ice allows you to update your applications and Slice definitions in a backwards-compatible manner - in such a way that the existing applications (using the existing Slice definitions) and the updated applications (using the updated Slice definitions) can easily and safely communicate with each other.

You can safely update your Slice using the techniques described on this page:

Adding Operations

Suppose that we have deployed our file system application and want to add extra functionality to a new version. Specifically, let us assume that the original version (version 1) only provides the basic functionality to use files, but does not provide extra information, such as the modification date or the file size. The question is then, how can we upgrade the existing application with this new functionality? Here is a small excerpt of the original (version 1) Slice definitions once more:

Slice
// Version 1
module Filesystem
{
    // ...

    interface File extends Node
    {
        idempotent Lines read();
        idempotent void write(Lines text) throws GenericError;
    }
}

Your first attempt at upgrading the application might look as follows:

Slice
// Version 2
module Filesystem
{
    // ...

    class DateTime extends TimeOfDay    // New in version 2
    {
        // ...
    }

    class Times                         // New in version 2
    {
        DateTime createdDate;
        DateTime accessedDate;
    }

    interface File extends Node
    {
        idempotent Lines read();
        idempotent void write(Lines text) throws GenericError;

        idempotent Times getTimes();    // New in version 2
    }
}

Note that the version 2 definition does not change anything that was present in version 1; instead, it only adds two new types and adds an operation to the File interface. Version 1 clients can continue to work with both version 1 and version 2 File objects because version 1 clients do not know about the getTimes operation and therefore will not call it; version 2 clients, on the other hand, can take advantage of the new functionality. The reason this works is that the Ice protocol invokes an operation by sending the operation name as a string on the wire (rather than using an ordinal number or hash value to identify the operation). Ice guarantees that any future version of the protocol will retain this behavior, so it is safe to add a new operation to an existing interface without recompiling all clients.

However, this approach contains a pitfall: the tacit assumption built into this approach is that no version 2 client will ever use a version 1 object. If the assumption is violated (that is, a version 2 client uses a version 1 object), the version 2 client will receive an OperationNotExistException when it invokes the new getTimes operation because that operation is supported only by version 2 objects.

Whether you can make this assumption depends on your application. In some cases, it may be possible to ensure that version 2 clients will never access a version 1 object, for example, by simultaneously upgrading all servers from version 1 to version 2, or by taking advantage of application-specific constraints that ensure that version 2 clients only contact version 2 objects. However, for some applications, doing this is impractical.

Note that you could write version 2 clients to catch and react to an OperationNotExistException when they invoke the getTimes operation: if the operation succeeds, the client is dealing with a version 2 object, and if the operation raises OperationNotExistsException, the client is dealing with a version 1 object.

Optional Operation Parameters and Class Data Members

You can safely add one or more optional parameters to an operation without breaking clients or servers that don't know anything of these new parameters.

For example, we could add an optional parameter to the read operation in our Filesystem File interface to optionally limit the number of lines returned:

Slice
interface File extends Node
{
    idempotent Lines read(optional (1) int max);
    ...
}

A client using the updated Slice definition can set this maximum value, and if its request is received by a new server, the server will receive this max value and should behave accordingly.  An old server wouldn't receive this optional parameter, and would continue to behave as before.

You can likewise add optional data members to an existing class without breaking existing applications that use this class. Let's go back to the FileSystem version 2 presented above - say we need to add a third data member to Times, while preserving existing applications that use the existing two-data-members Times. The solution is to add an optional data member, for example:

Slice
 class Times
{
     DateTime createdDate;
     DateTime accessedDate;
     optional (1) DateTime modifiedDate;
 }

Older applications that know nothing about modifiedDate will continue to receive, create, send (etc.) Times instances as before (without a modifiedDate data member), while newer applications can set and retrieve the optional modifiedDate member.

See Also