3

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

例如

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 都有公共的 final 字段,没有 getter 或 setter。(如果您是 getter 的粉丝,请假装这些字段对 getter 是私有的。)

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

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

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

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

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

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

4

2 回答 2

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 和 setter,继承和组合是不可能的。而且,这些对象很容易用 MessagePack 读写:

MessagePack msgpack = MessagePackTools.newGrainsMessagePack();

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

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

于 2013-06-25T04:08:06.240 回答
0

听起来您已经合并而不是分离应用程序的读写关注点。此时您可能应该考虑 CQRS。

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

您的域可能大部分应该是可变的,但您仍然不需要 getter 和 setter。相反,您应该在对象上使用动词,从而修改域模型,并在域中发生有趣的事情时引发事件(对业务感兴趣 - 业务 == 有人为您的时间付费)。这可能是您对通过网络传递感兴趣的事件,而不是域对象。甚至可能是命令(这些类似于事件,但源是域所在的有界上下文外部的代理——事件是模型有界上下文的内部)。

您可以拥有一项服务来持久化事件(以及另一个服务来持久化命令),这也是您的审计日志(完成您的审计故事)。

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

你真的不应该暴露你的领域模型的内部状态。你这样做是在破坏封装,而这并不是一件真正可取的事情。如果我是你,我会看看Axon Framework。它可能比单独的 MessagePack 更能让您走得更远。

于 2013-06-05T06:59:30.917 回答