Now that you know everything you need to know about
serialization and POF, let's discuss the closely related subject of
schema evolution support in Coherence.
The fact of life is that things change, and this
applies to domain objects as well. New releases of an application
usually bring new features, and new features typically require new
attributes within domain objects.
In a typical non-Coherence application, you can
introduce new columns into database tables, add attributes to your
classes, modify ORM configuration files, or custom database access logic
and be on your way. However, when your objects are stored within a
Coherence cluster, things are not that simple.
The main reason for that is that Coherence stores
objects, not raw data, and objects have a specific serialization format,
be it POF or something else. You can modify the database schema, your
classes, and O-R mapping code just as you used to, but you will also
have to decide what to do with the objects stored within Coherence
caches that were serialized in a format that is likely incompatible with
the serialization format of your modified classes.
There are really only a two possible choices you can make:
Shut down the whole cluster, perform an upgrade, and reload the data into the cache using the latest classes
Make your classes evolvable
It is important to note that there is nothing wrong
with the first option and if your application can be shut down
temporarily for an upgrade, by all means, go for it. However, many
Coherence-powered solutions are mission-critical applications that
cannot be shut down for any period of time, and many of them depend on
Coherence being the system of record.
In those cases, you need to be able to upgrade the
Coherence cluster node by node, while keeping the system as a whole
fully operational, so your only option is to add support for schema
evolution to your objects.
Implementing Evolvable objects
The first step you need to take when adding support
for schema evolution to your application is to make your data objects
implement the com.tangosol.io.Evolvable interface:
public interface Evolvable extends java.io.Serializable {
int getImplVersion();
int getDataVersion();
void setDataVersion(int i);
com.tangosol.util.Binary getFutureData();
void setFutureData(com.tangosol.util.Binary binary);
}
The Evolvable interface defines the
attributes each object instance needs to have in order to support the
schema evolution. This includes the following:
Implementation Version is the version number of the class implementation. This is typically defined as a constant within the class, which is then returned by the getImplVersion method. You need to increment the implementation version for a new release if you have added any attributes to the class.
Data Version is the version number of the class data.
You can also think of it as the version number of the serialized form
of a class. The Data Version attribute is set by the serializer during
deserialization, based on the version number found in the POF stream.
Future Data
is used to store attributes that exist within the POF stream but are
not read explicitly by the deserializer of an older version of a class.
They are simply stored within an object as a binary blob so they can be
written out into the POF stream during serialization, thus preventing
data loss during round tripping.
To illustrate all of this, let's return to our earlier example and see how we could add evolution support to the Customer class:
public class Customer
implements Entity<Long>, Evolvable {
// evolvable support
public static final int IMPL_VERSION = 1;
private int dataVersion;
private Binary futureData;
// data members
private Long m_id;
private String m_name;
private String m_email;
// constructors, getters and setters omitted for brevity
...
public int getImplVersion() {
return IMPL_VERSION;
}
public int getDataVersion() {
return dataVersion;
}
public void setDataVersion(int dataVersion) {
this.dataVersion = dataVersion;
}
public Binary getFutureData() {
return futureData;
}
public void setFutureData(Binary futureData) {
this.futureData = futureData;
}
}
It is immediately obvious that the implementation of
this interface will be the same for all evolvable classes, so it makes
sense to pull the dataVersion and futureData attributes into a base class and define the getImplVersion method as abstract. As a matter of fact, such a class already exists within coherence.jar, and is called AbstractEvolvable.
Using AbstractEvolvable as a base class greatly simplifies implementation of evolvable objects, as you only need to implement the getImplVersion method:
public class Customer
extends AbstractEvolvable
implements Entity<Long> {
// evolvable support
public static final int IMPL_VERSION = 1;
// data members
private Long m_id;
private String m_name;
private String m_email;
// constructors, getters and setters omitted for brevity
...
public int getImplVersion() {
return IMPL_VERSION;
}
}