Monday, October 25, 2010

Keeping Data Backward Compatible with Coherence POF

We have been using Oracle Coherence for a little over 2 years now. All things considered it has served us well - specifically in regard to keeping our data backward compatible. What I mean by "backwards compatability" is the ability to modify data points on existing domain objects without causing applications which don't yet require the new data points to be upgraded.


Coherence has a technology called "POF" (portable-object-format) which provides this capability. This article only dives into one of the many uses of POF, related to "backwards compatibility", as there are many other uses for POF that are not explored in this article. Also, before you continue reading please check out Paddy's video on Coherence if you aren't very familiar with what this technology does.



First off objects stored in Coherence must be serializable by one of the following formats:



Objects must be serializable because Coherence is a "distributed" object caching system, meaning that even if one JVM creates the object it will most likely end up being serialized across the network to another JVM responsible for caching it. Since our objects represent our "data", you need to make sure that you store your objects in an extensible manner so that data modifications can be made without causing a ripple effect of application deployments. Here at edmunds we have over 50+ different deployable applications (WAR, standard java app, etc) that use various distributed caches, so we cannot afford to re-deploy all of our applications if we are simply refactoring objects.



Taking the above into consideration when choosing a serialization format, Java serialization does have the ability to allow classes to be "versioned" to some extent by using the "serialVersionUID" (following these guidelines), it still is slow and produces a rather large object. Coherence has two additional forms of object serialization to help aid both performance and extensibility:

  • ExternalizableLite is an extension of Java Serialization with added basic compression.
  • POF(portable object format), on the other hand, offers much more sophisticated serialization format than that of standard Java serialization or ExternalizableLite.

    While POF has lots great features, the one feature we find very useful is that it allows object data to be "versioned" very easily. This has proven to be very useful to keep our data backward compatible so that no application is broken if another is upgraded or updated.


    Take the example below with call "Person":

    public class Person extends AbstractEvolvable implements
    EvolvablePortableObject {

    private String firstName;
    private String lastName;

    private static final int POF_DATA_VERSION = 1;


    private static final int FIRST_NAME_POF_INDEX = 1;
    private static final int LAST_NAME_POF_INDEX = 2;

    /**
    * {@inheritDoc}
    * Coherence uses this method before calling writeExternal().
    */
    public int getImplVersion() {
    return POF_DATA_VERSION;
    }

    public void readExternal(PofReader pofReader) throws IOException {
    firstName = pofReader.readString(FIRST_NAME_POF_INDEX);
    lastName = pofReader.readString(LAST_NAME_POF_INDEX);
    this.setFutureData(pofReader.readRemainder());
    }

    public void writeExternal(PofWriter pofWriter) throws IOException {
    // POF_DATA_VERSION is written automatically by Coherence before
    // calling this method.

    pofWriter.writeString(FIRST_NAME_POF_INDEX, firstName);
    pofWriter.writeString(LAST_NAME_POF_INDEX, lastName);
    if(this.getFutureData() != null) {
    pofWriter.writeRemainder(this.getFutureData());
    }
    }
    }


    "Person" is using POF (POF_DATA_VERSION = "1") and has two properties: firstName and lastName. Now let's assume that the serialized "Person" object is now live in production and being used by several applications. Now let's say that gender becomes a critical property of the "Person" object and we need to add it. Now the call will look like:

    public class Person extends AbstractEvolvable implements
    EvolvablePortableObject {

    private String firstName;
    private String lastName;

    // New POF field
    private String gender;

    // Up the data version to 2
    private static final int POF_DATA_VERSION = 2;


    private static final int FIRST_NAME_POF_INDEX = 1;
    private static final int LAST_NAME_POF_INDEX = 2;

    // New POF index for gender.
    private static final int GENDER_POF_INDEX = 3;

    public int getImplVersion() {
    return POF_DATA_VERSION;
    }

    public void readExternal(PofReader pofReader) throws IOException {
    firstName = pofReader.readString(FIRST_NAME_POF_INDEX);
    lastName = pofReader.readString(LAST_NAME_POF_INDEX);

    // Only attempt to read gender if your POF data version
    // is at least equal to or greater than the version of the data
    // being read in via PofReader.
    if(pofReader.getVersionId() >= 2) {
    streetAddress = pofReader.readString(GENDER_POF_INDEX)
    }

    this.setFutureData(pofReader.readRemainder());
    }

    public void writeExternal(PofWriter pofWriter) throws IOException {

    pofWriter.writeString(FIRST_NAME_POF_INDEX, firstName);
    pofWriter.writeString(LAST_NAME_POF_INDEX, lastName);

    // No logic here as we are just adding a field and our data is
    // always only written to the cache in one place - the JMS
    // listener.
    pofWriter.writeString(gender_POF_INDEX, gender);

    if(this.getFutureData() != null) {
    pofWriter.writeRemainder(this.getFutureData());
    }
    }
    }


    The updates above are:


    1. Add new "gender" field

    2. Add new POF index for "gender"

    3. Increment the data version

    4. Add logic to readExternal() method so that the new "gender" field is only read if you are working with data that is at the version that has the "gender" filed set



    Note that we hardcoded the version (2) that is required to read in the "gender" field. We did this so that when future modifications are made it is easy to determine what version a particular data point is compatible with.


    Another thing to point out is that you always should increment when adding additional fields to a "POF'able" object as there may be another version of that same object that is writing a totally different type of field at that index which would cause ClassCastExceptions, etc.

    Now both version 1 and 2 of the Person object can co-exist in production at the same time in our data grid and the applications that currently use version 1 will remain intact while we can create or update other applications to take advantage of the data available in version 2. In effect, versioning allows for peace of mind when it comes to deploying updated data objects or refactoring our data to accommodate new business requirements.



Hopefully this article showcases one of the ways in which POF can be useful as your serialization format. I say this because there are many other reasons to consider POF as your serialization format other than backwards compatibility alone.




No comments:

Post a Comment