Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure thrift objects are backward compatible?

Tags:

We're currently using thrift for developing our micro-services. When I recently came across this below issue.

Assume below is the thrift contract for Summary Object and there is an API which gets and updates summary using the summary object passed.

Version - 1.0

struct Summary {
    1: required string summaryId,
    2: required i32 summaryCost
}

Summary getSummary(1: string summaryId);

void updateSummary(1: Summary summary);

Now let's say there are 5 services which are using this 1.0 contract of Summary.
In the next release we add another Object called a list of summaryvalues.

So the new contract would look like

Version - 2.0

struct Summary {
    1: required string summaryId,
    2: required i32 summaryCost,
    3: optional list<i32> summaryValues
}

Summary getSummary(1: string summaryId);

void updateSummary(1: Summary summary);
  1. So when this below list is populated, we save the list of values summaryValues aganist that summaryId.
  2. And when the client sends this list as null we remove the existing values saved for that 'summaryId`.

Now the problem occurs when other services which are using OLDER version of the thrift contract (Version 1.0) try to call getSummary and updateSummary.
The intention of the Older Client by calling updateSummary was to set another value for summaryCost. However since this client doesn't contain the object summaryValues it sends the Summary object with summaryValues as null to Server.

This is resulting in the Server removing all existing values of summaryValues for that summaryId.

Is there a way to handle this in thrift? The isSet() Methods don't work here, as they try to perform a simple null check.
Every time we release a newer client with modification to existing objects, we're having to forcefully upgrade client versions of other servers even though the change is not related to them.

like image 444
Kishore Bandi Avatar asked Aug 22 '16 20:08

Kishore Bandi


1 Answers

In your version 2.0 the contract of the updateSummary() method has changed (ie, now it allows to save and remove summary values):

Option 1: Instead of changing its behaviour, create a new method (ie, updateSummaryV2()) and start using it across the latest version of your clients, while deprecating the old one.

This way, older versions of the client still use the normal updateSummary() without conflicting with new method's contract and assumptions.


Option 2: Add an optional field that contains the api version and has a default value set to the latest API version:

struct Summary {
    1: required string summaryId,
    2: required i32 summaryCost,
    3: optional list<i32> summaryValues
    4: optional i32 apiVersion = 2
}

This way, if apiVersion is not set, you know the request came from an old client, and for future versions, you will known the client version and can react accordingly.


Alternatively, you could only remove the records if an empty list is provided and do nothing if the list is not set, to respect the previous method contract.

As a side note: having an action depend on something implicit (here, the absence of a list) can be risky in general, even without considering the cross-compatibility issue. It's generally safer (and easier to work with and maintain) when such actions depend on an explicit flag.

like image 159
Shastick Avatar answered Sep 22 '22 16:09

Shastick