不可变业务对象和MessagePack消息之间的自动转换

时间:2011-10-31 06:51:34

标签: java serialization javabeans immutability messagepack

在Java中,我想使用不可变POJO的层次结构来表达我的域模型。

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)

所有这些POJO都有公共最终字段,没有getter或setter。 (如果你是吸气者的粉丝,请假装这些字段是私人的吸气剂。)

我还想使用MessagePack库序列化这些对象,以便通过网络传递它们,将它们存储到ZooKeeper节点等。

问题是MessagePack仅支持公共,非最终字段的序列化,因此我无法按原样序列化业务对象。 此外,MessagePack不支持enum,因此我必须将枚举值转换为intString以进行序列化。(是的,如果添加注释到enum s。请参阅下面的评论。)

为了解决这个问题,我有一个手写的“消息”对象的相应层次结构,每个业务对象和相应的消息对象之间都有转换。显然这不是理想的,因为它会导致大量的重复代码,而人为错误可能会导致缺少字段等。

这个问题有更好的解决方案吗?

  • 编译时生成代码?
  • 在运行时生成适当的可序列化类的某种方法吗?
  • 放弃MessagePack?
  • 放弃我的业务对象中的不变性和enum
  • 是否有某种通用的包装器库可以将可变对象(消息对象)包装成不可变对象(业务对象)?

MessagePack还支持Java Beans的序列化(使用@MessagePackBeans注释),因此,如果我可以自动将不可变对象转换为Java Bean,则可以使我更接近解决方案。

2 个答案:

答案 0 :(得分:1)

巧合的是,我最近创建的项目几乎完全符合您的描述。指某东西的用途 不可变数据模型提供了巨大的好处,但许多序列化技术似乎接近 不可改变作为事后的想法。我想要一些可以解决这个问题的东西。

我的项目Grains使用代码生成来创建不可变的实现 域模型。该实现足够通用,可以适应不同的序列化框架。 到目前为止,支持MessagePack,Jackson,Kryo和标准Java序列化。

只需编写一组描述您的域模型的接口即可。例如:

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();
}

Grains Maven插件然后在编译时生成这些接口的不可变实现。 (它生成的源设计为由人类读取。)然后,您可以创建对象的实例。这个例子 显示了两种构造模式:

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());

我知道,并不像你的public final字段那么简​​单,但如果没有getter,继承和组合是不可能的 和二传手。并且,使用MessagePack可以轻松读取和写入这些对象:

MessagePack msgpack = MessagePackTools.newGrainsMessagePack();

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

如果 Grains 框架不适合您,请随时检查其MessagePack templates。 您可以编写一个使用反射来设置手写域模型的最终字段的通用TemplateBuilder。诀窍 是创建一个允许注册自定义构建器的自定义TemplateRegistry

答案 1 :(得分:0)

听起来您已经合并了应用程序的读写问题,而不是将其分开。你应该考虑CQRS。

根据我的经验,不可变域对象几乎总是附加到审计故事(需求)或它的查找数据(枚举)。

您的域名可能大部分都是可变的,但您仍然不需要getter和setter。相反,你应该在你的对象上有动词,这会产生一个修改过的域模型,并且当域中发生一些有趣的事情时会引发事件(对业务感兴趣 - 业务= =有人为你的时间付费)。这可能是您有兴趣通过电线传递的事件,而不是域对象。也许它甚至是命令(这些命令与事件相似,但是源是你的域所在的有界上下文之外的代理 - 事件是模型有界上下文的内部事件。)

您可以使用服务来保留事件(以及另一个用于保留命令的服务),这也是您的审计日志(履行您的审计故事)。

您可以拥有一个事件处理程序,将您的事件推送到您的总线上。这些事件应包含简单信息或实体ID。响应这些事件的服务应使用提供的信息执行其职责,或者他们应使用给定的ID查询所需的信息。

您真的不应该暴露域模型的内部状态。你这样做会打破封装,这并不是一件令人满意的事情。如果我是你,我会看看Axon Framework。它可能比单独使用MessagePack更进一步。