Compatibility issues of go-storage

How to maintain go-service-* more easily? mentioned that problems may occur when the version of go-storage and go-service-* do not match.

I think this belongs to compatibility problem. Here’s a tracking issue Compatibility requirements · Issue #653 · beyondstorage/go-storage · GitHub

Let’s consider the compatibility problem from scratch.

backward compatibility as a library

First, for programming libraries, there are backward compatibility issues: What happens when users update the library?

Backwards compatibility is also required by go modules (only for compile-time?). There’s no forward compatibility issues for programming libraries.

compatibility when we offer two things that are used together

However, the problem become tricker when we offer interrelated libraries:

user application
├─ go-storage (version a)
└─ go-service-* ─ go-storage (version b)

The relationship isn’t that go-service-* uses go-storage, but go-service-*:

  • implements the interface of go-storage
  • uses go-storage code generator, part to implement the interface, part to implement other magical things.

So they are coupled in some ways.

When version b>a, it is probable that nothing breaks. We should consider the case when b<a.

interface

This is a general problem when exporting public interfaces.
The goblog’s advice is: just don’t change it. Keeping Your Modules Compatible - The Go Blog.

I think cases are:

  • new interface: totally compatible. And users can test easily whether a service implements an interface, without worrying about the versions.

  • new op in a interface. This is resolved by types: Add UnimplementedStub to have forward compatible implementations by Xuanwo · Pull Request #524 · beyondstorage/go-storage · GitHub. Then it is almost the same as the first case.

  • remove/modify existing interfaces. This breaks the basic compile time compatibility.

  • update interface specification. This is like the run-time compatibility problem stated above (i.e., what if the documented behavior changes) and is the trickest case. Different from a library changes documented behabior, if an interface changes specification, the implementation may mismatch (especially when they are decoupled).

    Example: go-storage/134-write-behavior-consistency.md at fe3103c3664ef6ed2dde9c3c38cd5500679989a6 · beyondstorage/go-storage · GitHub

    Imagine a user uses go-service-a and go-service-b, which use go-storage version a and b correspondingly. We updated the specification in go-storage version c. If a < c < b, users may see unexpected behaviors.

    If the specification update is relatively old, and users use relatively new versions of go-service-*, it is probable that this doesn’t happen. But there’s a dangerous transition period for new specification updates.

    BTW, this can be further divide into: undocumented->documented and behavior change. The first case seems less dangerous.

special features

I can think of the example of dynamic registering (loading?): connection string go-storage/90-re-support-initialization-via-connection-string.md at fe3103c3664ef6ed2dde9c3c38cd5500679989a6 · beyondstorage/go-storage · GitHub

go-storage adds a new API, which needs go-service-* to implicitly implement it (register in init()). Similarly the case when a < c < b will lead to problems.

Are there similar situations like this?

So, …

What extend of compatibility guarantee should we offer?

2 Likes

BTW, it just came to my mind that if we obey very strict compatibility rules, i.e., release a major version when any small changes happen, then users may think there are huge changes and hesitate to update. :thinking:

(So warning is an option, as @xuanwo said here How to maintain go-service-* more easily? - #4 by xuanwo

grpc may have the same problem, how do they resolve it?

For grpc,

Which one (or other problems) are you interested in?

1 Like