Archived

This forum has been archived. Please start a new discussion on GitHub.

Using facets for versioning

In the Ice document "32.Facets and Versions" chapter is mentioned facets can handle versioning requirements more elegantly like changing the parameters of an operation or modifying the fields of a structure. Unfortunately I haven't found example of implementing this on a server side either in demos or in other sources. As in documentation mainly client side is touched could you please describe server side little more or point me to a some example which might exist?

Thank you in advance.

Comments

  • matthew
    matthew NL, Canada
    Take a look at Michi's article "Can a leopard change its spots" in issue 8 of Connections (http://www.zeroc.com/newsletter/issue8.pdf).
  • I've taken a look on article "Can a leopard change its spots" but it does not contain much more information than Ice documentation. What I'm seeking is simplifying client/server update and looking in the forum I'm not alone. For me facets are not very different from servant's implementation point of view. In most cases smooth upgrading from version A to version B means that client or server has to implement both A and B functionality, which can be thrown away after they both become B. It seems that in most cases it's more easily to implement A&B support on client side and it's obvious enough. However if someone has to implement A&B functionality on server side he or she will be interested in reusing existing functionality. As it is said in your documentation “Quite often, the implementation of a version 2 facet in the server can even re-use much of the version 1 functionality, by delegating some version 2 operations to a version 1 implementation.”
    Meantime from a practical point of view I see more better way to do vice versa: implement new functionality in version 2 operations and re-use them from version 1 operations. In most cases new functionality is not quite different from previous and it often can survive using sentinel information, like empty string, 0, None, etc. After upgrading all components server implementation of version 1 could be easily dropped. It would be interesting to hear your comments.
    class HelloV2I : virtual public Demo::HelloV2
    {
    public:
    
        virtual void sayHello(::Ice::Int,
                              ::Ice::Float,
                              const Ice::Current&) const;
    };
    typedef ::IceUtil::Handle<HelloV2I> HelloV2IPtr;
    class HelloI : virtual public Demo::Hello
    {
    public:
        HelloI(HelloV2IPtr helloIV2) : m_helloIV2(helloIV2) {}
        virtual void sayHello(::Ice::Int,
                              const Ice::Current&) const;
        HelloV2IPtr m_helloIV2;
    };
    
    
    void
    HelloI::sayHello(::Ice::Int m,
                           const Ice::Current& current) const
    {
        m_helloIV2->sayHello(m, 0.f, current);
    }
    
    
    void
    HelloV2I::sayHello(::Ice::Int m,
                             ::Ice::Float n,
                             const Ice::Current& current) const
    {
        cout << "int " << m << " and float " << n <<  endl;
    }
    
    HelloV2IPtr helloi = new HelloV2I;
    adapter->add(helloi, communicator->stringToIdentity("helloV2"));
    adapter->add(new HelloI(helloi), communicator->stringToIdentity("hello"));
    
  • Hi Oleh,

    whether you implement version 2 functionality in terms of version 1 or vice versa really depends on your situation--how the code is structured, how close version 1 and 2 are to each other in terms of functionality, and so on.

    Usually, it's easier to implement version 2 in terms of version 1 simply because version 1 is there first. On the other hand, if you implement version 2 in terms of version 1, when it comes time to add version 2, you have to also change the implementation of version 1, which is more work.

    But then, as you say, if you do that, it makes it easier to drop version 1 later.

    The example you show is probably not that realistic, in the sense that all the functionality sits directly in the servant. For more complex operation implementations, what often makes sense is to have the servant delegate the work via normal language-native calls to helper classes that contain the application logic. That way, version 1 calls methods on the helper classes, and version 2 does the same. This means that version 1 and 2 are not directly implemented in terms of each other, but in terms of the same helper classes.

    Following this pattern effectively turns each Ice servant into a facade that dispatches into the helper classes. Such a design is more flexible and it makes it easier to add and remove versions over time.

    Really, how you implement this in detail is determined by your versioning requirements and how you structure your code. The job of facets is to keep the type system clean and to provide a way for you to distinguish between versions; how you implement the facets is up to you.

    Cheers,

    Michi.
  • Thank you for your quick and detailed response. Of course my example is not real one though I'm interested in overall idea whether such approach could be used and whether there are any obstacles. Either servants pass information further or implement functionality inside them it seems useful to have a possibility to turn versionA to versionB Ice calls as soon as possible. Introducing new version means splitting code and as far diversity goes as much code need to be split, mostly in copy/paste manner. Regarding this I have a question about AMD_ callbacks. Even using some helper class callbacks can go far deep and split code along their way. It looks like AMD_callbacks doesn't have common “official” ancestor and thus we can't use polymorphism. Could you please suggest the way how it can be accomplished?
    HelloV2I::sayHello_async(const AMD_HelloV2_sayHelloPtr& sayHelloCB,
                                     ::Ice::Int m,
                                     ::Ice::Float n,
                                     const Ice::Current& current)
    { 
     helper_call(sayHelloCB, m, n);
    }
    
    How can we pass sayHelloCB of type AMD_HelloV1_sayHelloPtr in HelloV1I servant to helper_call? Thank you in advance.
  • How can we pass sayHelloCB of type AMD_HelloV1_sayHelloPtr in HelloV1I servant to helper_call? Thank you in advance.

    I'm afraid that, because the two callbacks are distinct types, there is no way to do this. You will have to implement the callback for each version separately. However, if there is common functionality in the implementation of the two callbacks, you can make each callback implementation call into common helper classes that do the real work, just as you can implement servants that way.

    Alternatively, you could pass a pointer to member function, which will work if the operation in the two versions has the same parameters.

    Cheers,

    Michi.
  • matthew
    matthew NL, Canada
    Another method which Michi didn't mention is a C++ template. If the parameters of the slice operation don't change you could use a template to implement the common functionality.
  • Yes, I've already chosen C++ template approach, unfortunately old gcc template in template syntax is not so good and code looks little ugly. Waiting for template aliases :)