Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automated conversion between immutable business objects and MessagePack messages

In Java, I would like to use hierarchies of immutable POJOs to express my domain model.

e.g.

final ServiceId id = new ServiceId(ServiceType.Foo, "my-foo-service")
final ServiceConfig cfg = new ServiceConfig("localhost", 8080, "abc", JvmConfig.DEFAULT)
final ServiceInfo info = new ServiceInfo(id, cfg)

All of these POJOs have public final fields with no getters or setters. (If you are a fan of getters, please pretend that the fields are private with getters.)

I would also like to serialize these objects using the MessagePack library in order to pass them around over the network, store them to ZooKeeper nodes, etc.

The problem is that MessagePack only supports serialization of public, non-final fields, so I cannot serialize the business objects as-is. Also MessagePack does not support enum, so I have to convert enum values to int or String for serialization. (Yes it does, if you add an annotation to your enums. See my comment below.)

To deal with this I have a hand-written corresponding hierarchy of "message" objects, with conversions between each business object and its corresponding message object. Obviously this is not ideal because it causes a large amount of duplicated code, and human error could result in missing fields, etc.

Are there any better solutions to this problem?

  • Code generation at compile time?
  • Some way to generate the appropriate serializable classes at runtime?
  • Give up on MessagePack?
  • Give up on immutability and enums in my business objects?
  • Is there some kind of generic wrapper library that can wrap a mutable object (the message object) into an immutable one (the business object)?

MessagePack also supports serialization of Java Beans (using the @MessagePackBeans annotation), so if I can automatically convert an immutable object to/from a Java Bean, that may get me closer to a solution.

like image 399
Chris B Avatar asked Apr 14 '26 20:04

Chris B


1 Answers

Coincidentally, I recently created a project that does pretty much exactly what you are describing. The use of immutable data models provides huge benefits, but many serialization technologies seem to approach immutability as an afterthought. I wanted something that would fix this.

My project, Grains, uses code generation to create an immutable implementation of a domain model. The implementation is generic enough that it can be adapted to different serialization frameworks. MessagePack, Jackson, Kryo, and standard Java serialization are supported so far.

Just write a set of interfaces that describe your domain model. For example:

public interface ServiceId {
    enum ServiceType {Foo, Bar}

    String getName();
    ServiceType getType();
}

public interface ServiceConfig {
    enum JvmConfig {DEFAULT, SPECIAL}

    String getHost();
    int getPort();
    String getUser();
    JvmConfig getType();
}

public interface ServiceInfo {
    ServiceId getId();
    ServiceConfig getConfig();
}

The Grains Maven plugin then generates immutable implementations of these interfaces at compile time. (The source it generates is designed to be read by humans.) You then create instances of your objects. This example shows two construction patterns:

ServiceIdGrain id = ServiceIdFactory.defaultValue()
    .withType(ServiceType.Foo)
    .withName("my-foo-service");

ServiceConfigBuilder cfg = ServiceConfigFactory.newBuilder()
    .setHost("localhost")
    .setPort(8080)
    .setUser("abc")
    .setType(JvmConfig.DEFAULT);

ServiceInfoGrain info = ServiceInfoFactory.defaultValue()
    .withId(id)
    .withConfig(cfg.build());

Not as simple as your public final fields, I know, but inheritance and composition are not possible without getters and setters. And, these objects are easily read and written with MessagePack:

MessagePack msgpack = MessagePackTools.newGrainsMessagePack();

byte[] data = msgpack.write(info);
ServiceInfoGrain unpacked = msgpack.read(data, ServiceInfoGrain.class);

If the Grains framework doesn't work for you, feel free to inspect its MessagePack templates. You can write a generic TemplateBuilder that uses reflection to set the final fields of your hand-written domain model. The trick is to create a custom TemplateRegistry that allows registration of your custom builder.

like image 92
cambecc Avatar answered Apr 17 '26 08:04

cambecc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!