SDK Design Goal #3: Design for Backward Compatibility

This is a fourth article in the SDK Design Goal series. Please see the introduction article “How to present the licensed technology the right way?.

This goal works together with the prior one, Design for Extensibility. Let’s assume your SDK has passed the evaluation, the partner has integrated your product and is shipping. They are happy with the SDK quality, and everything goes well.

Then the other team in your organization asks you to extend the SDK just a bit. For example, they want the AV scanning function to return more information about the file. Or the OCR function to notify you about the progress. A pretty simple modification, which requires very slight modification of the API. Of course this would require a slight modification of the code using the SDK, but the internal team is ok with that. After all, they requested them, and they are ready and willing to modify their corresponding code using this SDK. And the modification is very small, so it is not a big real, right?

Unfortunately this is not right, and it is indeed a big deal for the partners. They put significant effort into studying your SDK, reading the documentations, integrating your code, and testing it. And partners prefer not to do it more than once. Thus it is safe to assume that nobody would ever touch this code again unless one of the following happens:

  • A bug has been found in this part of code, which requires reviewing the integration code; or
  • Partner’s product marketing requested a new feature to be implemented, which happens to be already provided by the SDK but requires additional integration.

Those are two cases the partner would understand the need to review and modify the integration code. The case when the API has changed the way which forces them to do so, is not such case. If you push those changes to the partner and this break their product build, the partner is not going to be happy. Please consider the impact: this may be a minor change for you, but only because you work with this SDK daily, and discussed this feature many times in last few weeks. The partner, in turn, might not have seen your SDK for several months, and even the developer who integrated it might have moved on. All the partner sees is that when they got a new SDK from you, it broke their build, and now someone needs to spend half-day to find out:

  • What was there before (in the old version), and what is there now (in a new version)?
  • How do they need to change their code to keep it working as before? For example, if you added a progress callback, but they could care less about it, could they just pass an empty/null callback, or they have to actually write some code (such as returning some value) to make sure the code will still work?
  • What kind of testing this change would require, and are the QA resources available?

As you see, what looked initially like a small, innocent change, suddenly created some major efforts on the partner side. Thus a new feature in your SDK, which the partner does not need, but which requires them to recompile the code is NOT what most partners would appreciate.

On the other hand, if you create a new feature in a branch, and start maintaining two versions of the SDK, this will quickly become unmanageable when more and more changes are requested.

To avoid this hassle, design your interface and the API so it stays backward compatible despite newly introduced changes. Then the partners never need to recompile (or, worse, change) their code to use a new version, unless they want to use the new features. This is achieved differently in different programming languages, but common to all is the following:

1. When you design the API, make notes on which functions or data blocks are not extendable without breaking the API. This depends on language; in C/C++ this for example would be structures with no size member.

2. Look at those blocks with your team and mark those which might be reasonably extended in future. Typical extensions usually are caused by:

  • Wanting more information from the SDK internals;
  • Applying SDK processing to different objects (files, memory buffers, IOStreams etc);
  • Adding more in-progress information during long running operations, usually with ability to interrupt the operation (this is a very common API breaking change)
  • Adding more debugging or logging to see what’s going on inside the SDK;
  • Adding more configuration options;

3. Plan in advance what would you do to preserve binary compatibility when you receive this request. For example, to handle the cases above in C/C++, you can:

  • Add the size or version member to the structures or initialization function, so they could be extended without breaking backward compatibility.
  • Use C prototypes instead of C++ classes; they allow more flexibility in modifications, where needed;
  • Have flexible SetOption/GetOption type of function accepting the reasonably largest possible argument (for example, if your SetOption only accepts integer but you need for some reason to pass a 64-bit number, you would have to add another function);
  • Use bit flags for the functions which accept per-call options, and reserve enough bits for new flags.
This entry was posted in Uncategorized.

Comments are closed.