在事件处理程序和回调
我的问题如何处理版本的对象:
是否有处理不同的版本已在处理程序或回调反序列化对象的最知名的方法是什么?在事件处理程序和回调
一些背景信息:
我们将要使用序列化对象作为消息在软件套件各个组件之间的通信。这些可能是JSON格式或使用类似protobufs的东西。每当你开始序列化对象,无论是长期存储还是不同版本的应用程序之间,你都必须能够处理这些对象的不同版本(可能使用Annotations-Java或Attributes-C#)。
我试图避免这样的代码:
onRecvMyMsg(MyMsg msg)
{
if (msg.version == 1.0)
// process it here
else if (msg.version < 1.5)
// process it here
else if (msg.version < 2.0)
// process part of it in the 1.5 handler and the other part here
else if // etc...
}
看起来这将是许多附加/增强/更改后维护的噩梦...
当然,必须有人已经因为这解决了这个似乎是软件工程中非常普遍的做法。任何帮助或建议,将不胜感激!
我原来的方法存在的问题是,解决方案是面向错误的方向。我们认为处理实体或对象的消费者需要知道版本,以便它能够正确处理它们之间的差异。 我们可以想到的是,我们如何根据处理器(或消费者)的版本获得表达自己的对象。
如果我们使用像Protocol Buffers,Apache Thrift或Apache Avro这样的序列化技术,我们就可以获得我们想要的东西了。像这些库在某种意义上处理我们的版本。一般来说,他们的行为是这样的:
- 如果接收到现场,但没有定义它仅仅是被丢弃
- 如果一个字段定义但没有收到,一个标志,表明它是 不存在,并且可选默认值可以提供
这些库还支持“required”字段;然而,大多数人(包括作者)不建议在协议对象本身上使用“必需”字段,因为如果存在“所需”字段,“必需”字段将在所有时间破坏可比较性,两种方式(发送和接收)字段不存在。他们建议在处理方面处理必需的字段。
由于提到的库处理以向后和向前兼容的方式对对象进行序列化和反序列化所需的所有工作,我们真正需要做的就是将这些协议对象包装到其他可以以下列形式公开数据的其他对象中:消费者期望。
例如,以下是可以处理的相同消息的3个版本。
ReviewCommentMsg // VERSION 1
{
string : username
string : comment
}
ReviewCommentMsg // VERSION 2 (added "isLiked")
{
string : username
string : comment
bool : isLiked
}
ReviewCommentMsg // VERSION 3 (added "location", removed "isLiked")
{
string : username
string : comment
string : location
}
以下演示了我们如何逐步更新客户端代码来处理这些消息。
/*******************************************************************************
EXAMPLE OBJECT V1
*******************************************************************************/
class ReviewComment
{
private final String username;
private final String comment;
ReviewComment(ReviewCommentMessage msg)
{
// Throws exception if fields are not present.
requires(msg.hasUsername());
requires(msg.hasComment());
this.username = msg.getUsername();
this.comment = msg.getComment();
}
String getUsername() { return this.username; }
String getComment() { return this.comment; }
}
/*******************************************************************************
EXAMPLE PROCESSOR V1
*******************************************************************************/
public void processReviewComment(ReviewComment review)
{
// Simulate posting the review to the blog.
BlogPost.log(review.getUsername(), review.getComment());
}
/*******************************************************************************
EXAMPLE OBJECT V2
*******************************************************************************/
class ReviewComment
{
private final String username;
private final String comment;
private final Boolean isLiked;
ReviewComment(ReviewCommentMessage msg)
{
// Throws exception if fields are not present.
requires(msg.hasUsername());
requires(msg.hasComment());
this.username = msg.getUsername();
this.comment = msg.getComment();
if (msg.hasIsLiked())
{
this.isLiked = msg.getIsLiked();
}
}
String getUsername() { return this.username; }
String getComment() { return this.comment; }
// Use Java's built in "Optional" class to indicate that this field is optional.
Optional<Boolean> isLiked() { return Optional.of(this.isLiked); }
}
/*******************************************************************************
EXAMPLE PROCESSOR V2
*******************************************************************************/
public void processReviewComment(ReviewComment review)
{
// Simulate posting the review to the blog.
BlogPost.log(review.getUsername(), review.getComment());
Optional<Boolean> isLiked = review.isLiked();
if (isLiked.isPresent() && !isLiked.get())
{
// If the field is present AND is false, send an email telling us someone
// did not like the product.
Stats.sendEmailBadReview(review.getComment());
}
}
/*******************************************************************************
EXAMPLE OBJECT V3
*******************************************************************************/
class ReviewComment
{
private final String username;
private final String comment;
private final String location;
ReviewComment(ReviewCommentMessage msg)
{
// Throws exception if fields are not present.
requires(msg.hasUsername());
requires(msg.hasComment());
requires(msg.hasLocation());
this.username = msg.getUsername();
this.comment = msg.getComment();
this.location = msg.getLocation();
}
String getUsername() { return this.username; }
String getComment() { return this.comment; }
String getLocation() { return this.location; }
}
/*******************************************************************************
EXAMPLE PROCESSOR V3
*******************************************************************************/
public void processReviewComment(ReviewComment review)
{
// Simulate posting the review to the blog.
BlogPost.log(review.getUsername(), review.getComment());
// Simulate converting the location into geo coordinates.
GeoLocation geoLocation = GeoLocation.from(review.getLocation());
// Simulate posting the location to the blog.
BlogPost.log(review.getUsername(), geoLocation);
}
在这个例子中:
PROCESSOR V1可以接收消息(V1,V2和V3)
PROCESSOR V2可以接收消息(V1,V2,和V3) (V3)
该方法将兼容性问题置于消息对象本身中,并缓解客户端/处理器进行一系列版本检查。
无可否认,您仍然需要执行一些语义检查;然而,这似乎远比为每个客户端构建版本逻辑都麻烦。
在接受它以查看其他人是否有更好的方法之前,我会稍微等待一段时间。我很想看看有没有其他想法。 – akagixxer