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:
// 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:
// 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:
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:
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.