Java offers a nice protocol for storing and communicating the persisted information. In a distributed deployment environment the server code has to support both the new and old information when its anticipated the control over the deployment of different versions of the servers are not available. The code has to deal with both backward as well as forward compatibility. i.e. VERSION N should be able to read both VERSION N-1 & VERSION N+1 object.
Maintaining the backward compatibility is easy. i.e. Version N code can read Version N-1 code by maintaining the version attribute. The version attribute in the read() API will make sure that the reading of new information is skipped. This is quite straight forward to achieve through “java.io.Externalizable” interface provided java where we have the full control of the context information on what we need to save. But it becomes tricky when we try to read the information created by future versions. The problem with forward compatibility is that the Java de-serialization it has to know what the new stuff that has been added & also should know where the information has been added.
The solution explained below tries to solve the problem in a pure java with less overhead & suitable for systems that have already established using Java serialization with ”java.io.Externalizable” interface.
Here the top down approach is followed for the explanation, it starts with how the usage of API should look like VERSION 0,1 & 2 then show how that can be achieved through code.
The challenge here is to read VERSION-1 (N) data from VERSION-0 (N-1) class. The current solution targets supporting only N+1 version & not the future versions which is the only important part of 24 X 7 support systems in the event of release back out.
Let's take an example of class "Test" having 2 attribute in VERSION-0, VERSION-1 introduces a new attribute called "versionData1" & VERSION-2 introduces the one more new attribute "versionData2". To simulate the case the data is inserted in between the data. In real-time scenario we will have lots of classes with readExternal()/writeExternal() methods spread across all over the code, but usually controlled with single mother object.
SKIP_START & SKIP_END are the marker interfaces used to identify the new information and ObjectInputWrapper is the new class used to move the cursor to skip the details when we read new information from the old class.
"Test" is Externalizable class having 3 attributes. We will include a new attribute in the second version and see how a "Test" object with VERSION -0 (N) reads VERSION -1 (N-1) object.
This plugin architecture is quite scalable. ObjectInputWrapper can be injected with different serialization protocols like Google proto buffer, Oracle Coherence's POF, etc... using a using factory pattern like ObjectInputOutputProviderFactory that provides different implementation including custom logic to efficiently store & retrieve the information
VERSION = 0;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(VERSION);
out.writeInt(number);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput _in) throws IOException, ClassNotFoundException {
ObjectInputWrapper in = new ObjectInputWrapper(_in);
VERSION = in.readInt();
number = in.readInt();
name = (String) in.readObject();
}
"Test{VERSION=0, number=10, name=praveen}"
VERSION = 1
This class has new String attribute called versionData1 with the value "versionData1"
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(VERSION);
out.writeInt(number);
// Added for version1 in between
out.writeObject(new SKIP_START());
out.writeObject(version1Data);
out.writeObject(new SKIP_END());
out.writeObject(name);
}
A marker interface class has been included to identify the new information that the older version can safely ignore.
@Override
public void readExternal(ObjectInput _in) throws IOException, ClassNotFoundException {
ObjectInputWrapper in = new ObjectInputWrapper(_in);
VERSION = in.readInt();
number = in.readInt();
if(VERSION>=1) {
in.readObject(); // we can safely ignore skip signals
version1Data = (String)in.readObject();
in.readObject();
}
name = (String) in.readObject();
}
"Test{VERSION=1, number=10, versionData1=versionData1, name=praveen}"
VERSION = 2
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(VERSION);
out.writeInt(number);
out.writeObject(version1Data);
// Added for version2 data in between
out.writeObject(new SKIP_START());
out.writeObject(version2Data);
out.writeObject(new SKIP_END());
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput _in) throws IOException, ClassNotFoundException {
ObjectInputWrapper in = new ObjectInputWrapper(_in);
VERSION = in.readInt();
number = in.readInt();
if(VERSION>=1) {
if(VERSION==1){
in.readObject();
}
version1Data = (String)in.readObject();
if(VERSION==1)
in.readObject();
}
}
if(VERSION>=2) {
in.readObject();
version2Data = (String)in.readObject();
in.readObject();
}
name = (String) in.readObject();
}
"Test{VERSION=2, number=10, versionData1=versionData1, versionData2=versionData2, name=praveen}"
The skip data version can safely be ignored in future versions. It will be removed in write first and also from read on subsequent versions.
The 2 key classes used to implement are explained here.
The ObjectInputWrapper class will have read method implementations for each basic data type. The sample below shows just two methods reading and readObject for illustration. For real implementations, we need to create final ObjectInputReaderTemplate classes for each basic data type and reuse them in the read methods of ObjectInputWrapper & they can be cached to avoid lots of object creations. The profiling of this code showed that these light weight are pretty cheap to handle, JVM will make sure that there is overhead in terms of memory or the cpu by in lining them automatically.
This class makes use of generics and anonymous inner classes to move the cursor to skip the new information that version N was un-aware while reading.
One sample code having all the classes published in github