在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
,因此我必须将枚举值转换为int
或String
以进行序列化。enum
s。请参阅下面的评论。)
为了解决这个问题,我有一个手写的“消息”对象的相应层次结构,每个业务对象和相应的消息对象之间都有转换。显然这不是理想的,因为它会导致大量的重复代码,而人为错误可能会导致缺少字段等。
这个问题有更好的解决方案吗?
enum
?MessagePack还支持Java Beans的序列化(使用@MessagePackBeans注释),因此,如果我可以自动将不可变对象转换为Java Bean,则可以使我更接近解决方案。
答案 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更进一步。