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);
summaryValues
aganist that summaryId
.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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With