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