如何在事件处理程序和回调中处理版本化对象

时间:2016-06-28 15:48:03

标签: java c# json serialization

我的问题
是否有一种最着名的方法来处理已在处理程序或回调中反序列化的不同版本的对象?

一些背景信息
我们将使用序列化对象作为消息来在软件套件中的各个组件之间进行通信。这些可以是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...
}

在经过多次添加/改进/更改后,这似乎是一次维护噩梦...... 当然,有人必须解决这个问题,因为这似乎是软件工程中非常普遍的做法。任何帮助或建议将不胜感激!

1 个答案:

答案 0 :(得分:0)

我原来的方法存在的问题是解决方案朝向错误的方向。我们认为处理实体或对象的使用者需要知道版本,以便它能够正确处理它们之间的差异。 我们可以考虑的是,我们如何根据处理器(或消费者)的版本让对象表达自己。

如果我们使用Protocol BuffersApache ThriftApache Avro等序列化技术,那么我们就可以获得我们想要的一半。从某种意义上说,像这样的图书馆为我们处理版本控制。一般来说,他们的行为如下:

  • 如果收到某个字段但未定义,则只删除它
  • 如果定义了一个字段但未收到该字段,则表示该字段为 不存在,可以提供可选的默认值

这些图书馆也支持&#34; required&#34;领域;但是,大多数人(包括作者)并不建议使用&#34; required&#34;协议对象本身的字段,因为&#34; required&#34;如果有一个&#34;必要的&#34;那么这个领域将会一直打破可比性(发送和接收)。场不存在。他们建议在处理方面处理必填字段。

由于提到的库处理以向后和向前兼容的方式序列化和反序列化对象所需的所有工作,我们真正需要做的就是将这些协议对象包装成可以以表格形式公开数据的其他对象。消费者期望。
例如,以下是可以处理的同一消息的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)
PROCESSOR V3 可以接收消息(V3)

这种方法将兼容性问题放在消息对象本身中,并减轻客户端/处理器的大量版本检查 诚然,你仍然需要进行一些语义检查;但是,这似乎远不如为每个客户构建版本逻辑那么麻烦。