2016-06-28 81 views
1

我的問題如何處理版本的對象:
是否有處理不同的版本已在處理程序或回調反序列化對象的最知名的方法是什麼?在事件處理程序和回調

一些背景信息
我們將要使用序列化對象作爲消息在軟件套件各個組件之間的通信。這些可能是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... 
} 

看起來這將是許多附加/增強/更改後維護的噩夢...
當然,必須有人已經因爲這解決了這個似乎是軟件工程中非常普遍的做法。任何幫助或建議,將不勝感激!

回答

0

我原來的方法存在的問題是,解決方案是面向錯誤的方向。我們認爲處理實體或對象的消費者需要知道版本,以便它能夠正確處理它們之間的差異。 我們可以想到的是,我們如何根據處理器(或消費者)的版本獲得表達自己的對象。

如果我們使用像Protocol BuffersApache ThriftApache 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)

該方法將兼容性問題置於消息對象本身中,並緩解客戶端/處理器進行一系列版本檢查。
無可否認,您仍然需要執行一些語義檢查;然而,這似乎遠比爲每個客戶端構建版本邏輯都麻煩。

+0

在接受它以查看其他人是否有更好的方法之前,我會稍微等待一段時間。我很想看看有沒有其他想法。 – akagixxer