Accessing Domain Objects without Domain Classes - BETA
Hazelcast offers a generic object interface (GenericRecord
) that is returned to the user
when the domain class is missing on the classpath.
For example, if PortableFactory
is not given in the serialization configuration for a
portable object, the user domain class cannot be created, and Hazelcast returns GenericRecord
instead.
In the previous Hazelcast IMDG releases, we were throwing HazelcastSerializationException("Could not create Portable for class-id: " + classId)
for the same situation.
GenericRecord
is an immutable object. It allows you to read the field of objects via the related field names.
GenericRecord
is applicable only to Portable
objects.
You can use this feature when the cluster does not have the domain classes of the clients in a
client-server architecture. On remote calls like distributed executor service or entry processors,
you may need to access the domain object. In case the class of the domain object is not available on the cluster,
GenericRecord
allows to access, read and write the objects
back without the class of the domain object on the classpath. Here is a read example with entry processor:
map.executeOnKey(key, (EntryProcessor<Object, Object, Object>) entry -> {
Object value = entry.getValue();
GenericRecord genericRecord = (GenericRecord) value;
int id = genericRecord.getInt("id");
return null;
});
An alternative approach introduced in the previous Hazelcast IMDG releases is the User Code Deployment
feature to deploy the classes from the client to the cluster.
However, it has a restriction: you can not upload
a new version of your class to the cluster if you use the portable versioning support.
Loading two different versions of the same class on the JVM is not a problem that we want to solve: using GenericRecord
,
you can easily write different versions of your classes
from the clients and access them without using the User Code Deployment feature.
With the introduction of GenericRecord
, User Code Deployment should be used only for functional objects like Runnable
,
Callable
and EntryProcessor
.
You can also create a GenericRecord
in portable format with GenericRecordBuilder
as follows:
ClassDefinition classDefinition = new ClassDefinitionBuilder(PORTABLE_FACTORY_ID, EMPLOYEE_CLASS_ID)
.addStringField("name").addIntField("id").build();
GenericRecord namedRecord = GenericRecordBuilder.portable(classDefinition)
.setString("name", "foo")
.setInt("id", 123).build();
Note that the class definitions are better to be created once and
used when creating different instances of the same type GenericRecord
.
We have also added two convenience methods in GenericRecord
for you to
avoid passing a class definition. For example, if you want to modify a value and
put it back using an entry processor, you don’t need to create a class definition.
Instead you can create a builder from GenericRecord
which carries the same class definition as follows:
map.executeOnKey("key", (EntryProcessor<Object, Object, Object>) entry -> {
GenericRecord genericRecord = (GenericRecord) entry.getValue();
GenericRecord modifiedGenericRecord = genericRecord.newBuilder()
.setString("name","Kermit")
.setLong("id", 4)
.setInt("age",20)
.setString("surname", "The Frog").build();
entry.setValue(modifiedGenericRecord);
return null;
});
Another convenience method is cloneWithBuilder
. This is useful if you want to update only
a couple of fields from the original genericRecord
. In that case, the new builder carries
both classDefinition
and values from the original
genericRecord
. Here is the same example where we just update the age:
map.executeOnKey("key", (EntryProcessor<Object, Object, Object>) entry -> {
GenericRecord genericRecord = (GenericRecord) entry.getValue();
GenericRecord modifiedGenericRecord = genericRecord.cloneWithBuilder()
.setInt("age",22).build();
entry.setValue(modifiedGenericRecord);
return null;
});
Another use case of this feature is on the client side (could also be a member):
GenericRecord
allows to read from/write to a cluster without having the related classes on the classpath.
A client could work with the cluster without introducing the PortableFactory
at the start.
In this case, the client works with GenericRecord
s instead of domain classes.
An example code snippet on the client side with a map is shown below:
GenericRecord record = (GenericRecord) map.get("key1");
String name = record.getString("name");
int id = record.getInt("id");
GenericRecord newGenericRecord = record.cloneWithBuilder()
.setInt("age",22).build();
map.put("key2", newGenericRecord);