Compile-time Non-Intrusive Serializer

I want to share an exciting new feature of Hazelcast C++ Client 4.0, the “Compile-time Non-Intrusive Serializer.” As you may already know, Hazelcast supports multiple ways of serialization. You can serialize an object into bytes and deserialize bytes into objects in one of the following ways using Hazelcast API:

  1. identified_serializer: This is by far one of the most efficient serialization methods you can use. It simply puts an identifier for the serialized object which is a pair of integers, factory_id, class_id and you write your object serializer using the object_data_output and object_data_input class methods.
  2. portable_serializer: This serializer allows you to serialize objects using versioning. It also allows fetching individual fields of the object without having to deserialize in addition to querying and indexing support without deserialization. This serializer provides the portable_writer and portable_reader classes for you to code the object serialization.
  3. custom_serializer: This is the place you can plug in your third party or your own serializations.

Please see https://github.com/hazelcast/hazelcast-cpp-client#4-serialization for details of the serializations.

In our 3.x version clients, when you want to put an object into the cache, you still used one of these serialization methods but the determination of the serializer was being done during runtime and it was intrusive, i.e. you had to make changes to your object class and implement a certain interface. With the 4.0 release, we provide a non-intrusive way of serialization and the determination of the serializer is compile-time, hence there is no runtime cost of serializer search. How do we achieve this? We actually borrowed the idea from the standard library. You provide hash methods for the std::unordered_map. For inserting your object class my_class into an unordered_map as a key, you have to provide an implementation of std::hash<my_class> specialization of the hash struct. Hence, we also decided that if you want an Hazelcast serialization of your class my_class, you need to provide the specialization of hz_serializer<T> struct. This specialization should be defined in the hazelcast::client::serialization namespace and should be seen from the compilation unit of your source file. Furthermore, it should be derived from one of the following empty marker structs:

  1. identified_data_serializer
  2. portable_serializer
  3. custom_serializer

Here is an example of identified_serializer serialization of class my_class:

struct my_class {
    std::string name;
    bool male;
    int32_t age;
};

namespace hazelcast {
    namespace client {
        namespace serialization {
            template<>
            struct hz_serializer<my_class> : identified_data_serializer {
                static int32_t get_factory_id() noexcept {
                    return 1;
                }

                static int32_t get_class_id() noexcept {
                    return 3;
                }

                static void write_data(const my_class &object, object_data_output &out) {
                    out.write(object.name);
                    out.write(object.male);
                    out.write(object.age);
                }

                static my_class read_data(object_data_input &in) {
                    return my_class{in.read<std::string>(), in.read<bool>(), in.read<int32_t>()};
                }
            };
        }
    }
}

This way of specialization allows us two advantages:

1. You do not need to modify your classes (no extra API interfaces for existing classes).

2. The determination of the serializer is at compile time.

In addition to deriving from these marker classes, certain methods should be implemented for your code to compile with these serializations. In the above example, the methods get_factory_id, get_class_id, read_data and write_data methods will be needed in order that you can compile your project. Otherwise, you will get missing method compilation errors.

The compilation looks for hz_serializer<my_class> defined in the hazelcast::client::serialization namespace. If this is not found, it falls back to a global_serializer if configured and throws exception if no such configuration. 

You no longer need any configuration for adding a serializer and you do not need to make any changes to your existing classes. I hope that you find this new way of serialization convenient and easy to use.