Table of Contents Previous Next
Logo
Facets and Versioning : 34.6 Behavioral Versioning
Copyright © 2003-2009 ZeroC, Inc.

34.6 Behavioral Versioning

On occasion, versioning requires changes in behavior that are not manifest in the interface of the system. For example, we may have an operation that performs some work, such as:
interface Foo {
    void doSomething();
};
The same operation on the same interface exists in both versions, but the behavior of doSomething in version 2 differs from that in version 1. The question is, how do we best deal with such behavioral changes?
Of course, one option is to simply create a version 2 facet and to carry that facet alongside the original version 1 facet. For example:
module V2 {

    interface Foo {    // V2 facet
        void doSomething();
    };
};
This works fine, as far as it goes: a version 2 client asks for the "V2" facet and then calls doSomething to get the desired effect. Depending on your circumstances, this approach may be entirely reasonable. However, if there are such behavioral changes on several interfaces, the approach leads to a more complex type system because it duplicates each interface with such a change.
A better alternative can be to create two facets of the same type, but have the implementation of those facets differ. With this approach, both facets are of type ::Foo::doSomething. However, the implementation of doSomething checks which facet was used to invoke the request and adjusts its behavior accordingly:
void
FooI::doSomething(const Ice::Current& c)
{
    if (c.facet == "V2") {
        // Provide version 2 behavior...
    } else {
        // Provide version 1 behavior...
    }
}
This approach avoids creating separate types for the different behaviors, but has the disadvantage that version 1 and version 2 objects are no longer distinguishable to the type system. This can matter if, for example, an operation accepts a Foo proxy as a parameter. Let us assume that we also have an interface FooProcessor as follows:
interface FooProcessor {
    void processFoo(Foo* w);
};
If FooProcessor also exists as a version 1 and version 2 facet, we must deal with the question of what should happen if a version 1 Foo proxy is passed to a version 2 processFoo operation because, at the type level, there is nothing to prevent this from happening.
You have two options to deal with this situation:
• Define working semantics for mixed-version invocations. In this case, you must come up with sensible system behavior for all possible combinations of versions.
• If some of the mixed-version combinations are disallowed (such as passing a version 1 Foo proxy to a version 2 processFoo operation), you can detect the version mismatch in the server by looking at the Current::facet member and throwing an exception to indicate a version mismatch. Simultaneously, write your clients to ensure they only pass a permissible version to processFoo. Clients can ensure this by checking the facet name of a proxy before passing it to processFoo and, if there is a version mismatch, changing either the Foo proxy or the FooProcessor proxy to a matching facet:
FooPrx foo = ...;               // Get a Foo...
FooProcessorPrx fooP = ...;     // Get a FooProcessor...

string fooFacet = foo>ice_getFacet();
string fooPFacet = fooP>ice_getFacet();
if (fooFacet != fooPFacet) {
    if (fooPFacet == "V2") {
        error("Cannot pass a V1 Foo to a V2 FooProcessor");
    } else {
        // Upgrade FooProcessor from V1 to V2
        fooP = FooProcessorPrx::checkedCast(fooP, "V2");
        if (!fooP) {
            error("FooProcessor does not have a V2 facet");
        } else {
            fooP>processFoo(foo);
        }
    }
}
Table of Contents Previous Next
Logo