MadVikingGod / intexplore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Forward Compatibility In go

This is a repository of how forward compatibility breaks within go. This demonstrates how we can break compatibility, and how the different ways effect users of the API, and SDK.

Forward Compatibility

The definition for the scope of this document is: When a user of the the API and the SDK upgrades only the API and not the SDK, the program will continue to work.

Why is that important? In go the tooling assumes any minor version change is forward compatible, and will update uses of the API to the highest version used in any dependency. This means if you specify API & SDK v1.0.0, but you have a dependency that uses API V1.1.0 the go tooling will update to use API v1.1.0 and not change the SDK.

Repository Structure

There are three main directories: api, sdk, and client.

  • The api directory contains an API that we will make an addition to.
  • The sdk directory contains a matching SDK that won't be updated, it could, but the client will be able to upgrade the API (think via a dependency) with or without an SDK update.
  • The client directory contains a client that uses the SDK, and indirectly the API.

Under each of the main directories there are three subdirectories: abstract, direct and side.

  • The abstract directory contains an example if we embed the interface in the SDK structs. This will allow the client to upgrade, but any use of the new API will panic
  • The direct directory contains an example if we directly add the new method to the API. This will allow clients to upgrade, but not compile.
  • The side directory contains an example if we create a second interface that users of the API can assert to. This will allow clients to upgrade, but all uses of the new API require assertions, and checks.

Examples

The examples discussed below are from the client-v2 tag

Abstract

By embedding the interface the program does compile, but will panic when the new method is called. This can be mitigated by embedding a NoOp implementation.

$ go run .
v1
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x457ce4]

goroutine 1 [running]:
github.com/madvikinggod/intexplore/sdk/abstract.(*blank).V2(0x4649c3?)
        <autogenerated>:1 +0x24
main.main()
        /home/aaron/projects/intexplore/client/abstract/main.go:9 +0x7f
exit status 2

Direct

By directly adding the new method to the API the program will not compile. The other problem is that the error message doesn't directly point to the solution that the SDK needs to be updated.

$ go run .
# github.com/madvikinggod/intexplore/sdk/direct
/home/aaron/go/pkg/mod/github.com/madvikinggod/intexplore/sdk/direct@v1.0.1/blank.go:12:9: cannot use &blank{} (value of type *blank) as type "github.com/madvikinggod/intexplore/api/direct".Blank in return statement:
        *blank does not implement "github.com/madvikinggod/intexplore/api/direct".Blank (missing V2 method)

Side

By creating a second interface the program will compile, but the user of the API will need to assert to the new interface. This is the most flexible, but Interface Smuggling has other usability issues. See the ResponseController replacing the Hijacker and Flusher etc interfaces.

$ go run .
v1
blank is not V2
	if b, ok := b.(side.BlankV2); ok {
		println(b.V2())
	} else {
		println("blank is not V2")
	}

About


Languages

Language:Go 100.0%