How to maintain go-service-* more easily? mentioned that problems may occur when the version of
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.
First, for programming libraries, there are backward compatibility issues: What happens when users update the library?
- (Weak: compile-time) When users update to new version, their code still compiles. This is a very basic requirement. This means the API is stable. Public signatures should not be removed or modified. Examples of breaking this requirement:
- (Strong: run-time) When users update to new version, their code still gets same results. This is a very strong guarantee. This cannot be reached if the code is nondeterministic.
A weaker version is that: the code has the same documented behavior. In other words, if not documented, the code can do anything (like C++ UB?), and can of course change behavior when updating versions.
Usually the latter one is generally obeyed, even without formal statement, but users will sometimes take the strong version for granted.
One specific case is abstraction leak (Users rely on undocumented behavior), e.g., the API returns an interface, but the user converts it to a specific interface implementation.
binary_searchis documented nondeterministic, but the implementation was deterministic. When it is updated to a nondeterministic implementation, a user breaks. A Polkadot Postmortem - 24.05.2021
- go-storage/47-additional-error-specification.md at fe3103c3664ef6ed2dde9c3c38cd5500679989a6 · beyondstorage/go-storage · GitHub
Backwards compatibility is also required by go modules (only for compile-time?). There’s no forward compatibility issues for programming libraries.
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
- implements the interface of
go-storagecode generator, part to implement the interface, part to implement other magical things.
So they are coupled in some ways.
b>a, it is probable that nothing breaks. We should consider the case when
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).
Imagine a user uses
go-service-b, which use
go-storageversion a and b correspondingly. We updated the specification in
go-storageversion 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.
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?
What extend of compatibility guarantee should we offer?