RCF provides robust versioning support, allowing you to upgrade distributed components without breaking runtime compatibility with previously deployed components.
If you write components which only communicate with peer components of the same version, you won't need to worry about versioning. For instance, if all your components are built from the same codebase and deployed together, versioning won't be an issue.
However, if for example you have clients which need to communicate with servers that were built from an older version of the same codebase, and have already been deployed, then you will need to be aware of how RCF deals with versioning.
Unlike other RPC systems you may have encountered, RCF is designed to be versioning-friendly. The data contract between a client and a server is essentially defined by the following:
RCF allows you considerable scope to evolve all these aspects of your data contract, without invalidating your existing data contract.
Each method in an RCF interface has a method ID associated with it, which is used by clients to identify that particular method. The first method on an interface has a method ID of 0, the next one a method ID of 1, and so on.
Inserting a method at the beginning, or in the middle, of an RCF interface, changes the existing method ID's and hence breaks compatibility with existing clients and servers. To preserve compatibility, new methods need to be added at the end of the RCF interface:
Removing methods can be done as well, as long as a place holder is left in the interface, in order to preserve the method ID's of the remaining methods in the interface.
Parameters can be added to a method, or removed from a method, without breaking compatibility. RCF servers and clients ignore any extra (redundant) parameters that are passed in a remote call, and if an expected parameter is not supplied, it is default initialized.
Likewise, parameters can be removed:
Note that RCF marshals in-parameters and out-parameters in the order that they appear in the RCF_METHOD_XX()
declaration. Any added (or removed) parameters must be the last to be marshalled, otherwise compatibility will be broken.
RCF interfaces are identified by their runtime name, as specified in the second parameter of the RCF_BEGIN()
macro. As long as this name is preserved, the compile time name of the interface can be changed, without breaking compatibility.
The application-specific data types passed in a remote call, are likely to change over time. As those data types change, their serialization functions will change as well. To assist applications in maintaining backwards compatibility for serialization code, RCF provides an archive version number concept.
The archive version number is passed from client to server on every remote call, and is zero by default. To implement versioning, your application is expected to manage the archive version, essentially updating it whenever a breaking change is made to serialization code.
To set the archive version number, call RCF::setArchiveVersion()
before creating any servers or clients.
Alternatively, you can set the archive version number for individual clients and servers by calling RCF::ClientStub::setArchiveVersion()
and RCF::RcfServer::setArchiveVersion()
.
When a client connects to a server, an automatic version negotiation step takes place, to ensure that the connection uses the greatest archive version number that both components support.
The archive version number is intended to be used by serialization code. From within a serialization function you can retrieve the archive version number in use, by calling SF::Archive::getArchiveVersion()
:
The serialization code can then use the value of the archive version number to determine which members to serialize.
For example, assume that the first version of your application contains this code:
Once this version has been released, a new version is prepared, with a new member added to the X
class:
Notice that the serialization code of X
now uses the archive version number to determine whether it should serialize the new mB
member.
With these changes, new servers are able to process calls from both old and new clients, and new clients are able to call either old or new servers:
RCF maintains runtime compatibility with itself, for previous RCF releases dating back to and including RCF 2.0. Runtime compatibilty with RCF releases older than 2.0 is not guaranteed.
To implement runtime compatibility between RCF releases, RCF maintains a runtime version number, which is incremented for each RCF release. The runtime version number is passed in the request header for each remote call, and allows older and newer RCF releases to interoperate.
RCF's automatic client-server version negotiation handles runtime versioning, as well as archive versioning. In most circumstances, you won't need to know about runtime version numbers. You can mix and match RCF releases, and at runtime, an appropriate runtime version is negotiated for each client-server connection.
In some situations, the client may already know the runtime version and archive version of the server it is about to call, in which case it can disable automatic versioning and set the version numbers explicitly:
Automatic version negotiation is not supported for one-way calls. In particular, the one-way calls made by a publisher to its subscribers, are not automatically versioned. If you have subscribers with varying runtime and archive version numbers, the publisher will need to explicitly set version numbers on the publishing RcfClient<>
object, to match that of the oldest anticipated subscriber:
For reference, here is a table of runtime version numbers for each RCF release.
RCF release | Runtime version number |
---|---|
2.0 | 10 |
2.1 | 11 |
2.2 | 12 |
3.0 | 13 |
For applications with backwards compatibility requirements and short or continuous release cycles, archive versioning can become difficult to manage. Each increment of the archive version number involves adding new execution paths to serialization functions, and may over time lead to complicated serialization code.
RCF also supports Protocol Buffers, which provides an alternative approach to versioning. Rather than manually writing serialization code for C++ objects, Protocol Buffers can be used to generate C++ classes with built-in serialization code, which deals automatically with versioning differences (see Protocol Buffers).