A newer version of Hazelcast Platform is available.

View latest

Accessing Domain Objects Without Domain Classes

User Code Deployment has been deprecated and will be removed in the next major version. To continue deploying your user code after this time, Community Edition users can either upgrade to Enterprise Edition, or add their resources to the Hazelcast member class paths. Hazelcast recommends that Enterprise Edition users migrate their user code to use User Code Namespaces for all purposes other than Jet stream processing. For further information on migrating from User Code Deployment to User Code Namespaces, see Migrate from User Code Deployment.

Usually, to access any field in a domain object, you would need to have the class of that object on the member’s classpath. However, you may not want to add classes on the member. In this case, Hazelcast can return a GenericRecord object to your Java application. This object gives you access to your domain object’s fields without having to add a factory class to the classpath of your members or register a serializer for them.

Hazelcast is able to represent Portable and Compact serialized objects as GenericRecord.

For example, to access the fields of a domain object in an entry processor, you could do the following:

map.executeOnKey(key, (EntryProcessor<Object, Object, Object>) entry -> {
    Object value = entry.getValue();
    GenericRecord genericRecord = (GenericRecord) value;

    int id = genericRecord.getInt32("id");

    return null;
});

An alternative approach introduced in the previous Hazelcast releases is the User Code Deployment feature to deploy the classes from the client to the cluster. However, it has a restriction: you cannot 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.

Creating New Portable Objects

CAUTION: .Deprecation Notice for Portable Serialization

Portable Serialization has been deprecated. We recommend you use Compact Serialization as Portable Serialization will be removed as of version 7.0.

To create a GenericRecord object in portable format, use the GenericRecordBuilder.portable() method:

ClassDefinition classDefinition = new ClassDefinitionBuilder(PORTABLE_FACTORY_ID, EMPLOYEE_CLASS_ID)
                        .addStringField("name")
                        .addIntField("id")
                        .build();

GenericRecord namedRecord = GenericRecordBuilder.portable(classDefinition)
                .setString("name", "foo")
                .setInt32("id", 123)
                .build();

Note that the class definitions are better to be created once and used when creating different instances of the same GenericRecord object.

Creating New Compact Objects

To create a GenericRecord object in compact serialization format, use the GenericRecordBuilder.compact() method:

GenericRecord namedRecord = GenericRecordBuilder.compact("employee")
        .setString("name", "foo")
        .setInt32("id", 123)
        .build();

Note that there is no need to create a class definition, or a schema in this case. A schema will be created from the fields of the builder automatically.

Data mismatches can occur if you use both generic and specific Compact serializers. For example, the Compact Generic Record serializer, which is included with Hazelcast, writes variable size fields in alphabetic order based on the field name. To avoid potential mismatches in your data, write explicit variable size fields for Compact serializers in alphabetic order.

Adding and Changing Values in Domain Objects

We have also added two convenience methods in GenericRecord for you to avoid passing a class definition or re-calculating schema. 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 or pay the cost of re-calculating a schema. Instead, you can create a builder from the GenericRecord object which carries the same class definition or schema as follows:

map.executeOnKey("key", (EntryProcessor<Object, Object, Object>) entry -> {
    GenericRecord genericRecord = (GenericRecord) entry.getValue();
    GenericRecord modifiedGenericRecord = genericRecord.newBuilder()
            .setString("name", "Kermit")
            .setInt64("id", 4)
            .setInt32("age", 20)
            .setString("surname", "The Frog")
            .build();
    entry.setValue(modifiedGenericRecord);
    return null;
});

Another convenience method is newBuilderWithClone(). 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 or schema 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.newBuilderWithClone()
            .setInt32("age", 22)
            .build();
    entry.setValue(modifiedGenericRecord);
    return null;
});