How can I support multiple versions of same interface? - go

I am writing a go module that implements a struct that satisfies an interface. We only want to maintain a single version of our library, but our clients use multiple versions of one of our dependencies.
The dependency provides an interface we want to implement, like this.
type SuperCoolInterface interface {
DoOldCoolThing(value string)
}
And our implementation is like this.
type SuperCoolImpl struct {}
func (sc *SuperCoolImpl) DoOldCoolThing(value string) {}
The new version of the dependency adds a new type in a types module.
type NewType struct {
value string
}
The dependency adds a method to the interface.
type SuperCoolInterface interface {
DoOldCoolThing(value string)
DoNewCoolThing(value types.NewType)
}
Now if I implement the new method, it won't compile with the old version of the library, since types.NewType does not exist. However, if I don't implement the new version, I won't satisfy the new version of the interface.
type SuperCoolImpl struct {}
func (sc *SuperCoolImpl) DoOldCoolThing(value string) {}
func (sc *SuperCoolImpl) DoNewCoolThing(value types.NewType) {}
Do we need to fork our code in order to support this version? In languages with preprocessors there is an easy solution for this, so I am assuming Go must have a solution I am missing.
We are planning to continue development and support both versions, so it would be annoying to need to ensure two different version maintain consistency. I was hoping I could do something with reflection or something similar to C's preprocessor where I can define a preprocessor value and only implement the method when we indicate the version of the library has the correct type.

I figured out a solution that works for my situation.
Thanks #Burak Serdar for aiming me in the right direction.
My solution is to put the old implementation into impl/v0 package, and the new implementation into impl/v1 package.
The clients who use the old version of the dependency will use impl/v0, and the clients that use the new version of the dependency will use impl/v1.
Since golang only compiles the code that was imported directly, only the package with the correct version of the interface will be compiled, so will successfully compile in both directions.
The alleviates my concern about having to fork the entire library.
Edit: If anyone uses this solution there is one gotcha if you are currently running your tests with go test ./.... That command seems to try and build every module, regardless of if it has tests in it or not.
But you can exclude tests with go test $(go list ./... | grep -v <PATH_TO_IGNORE>), and then you can run those tests against the correct version in another command.

Related

Using instances of generic types as return value type in a Hyperledger chain code written in GO

In my project, I am using a wrapper structure, that is defined similar to this:
type Wrapper[T any] struct {
Foo int
Data T
}
Additionally, my chain code offers a method with the following signature
func(contract *MyContract) DoSomething() *Wrapper[mypkg.Bar]
where Bar is a simple structure defined - for example - like this:
package mypkg
struct Bar {
Foo string
Bar string
}
Anyhow, if I try to deploy my chaincode, I get the following error:
Error compiling schema for MyContract[DoSomething]. Return schema invalid. Object has no key 'Wrapper[[]<part of module name>'
Strangely, the part with Wrapper[[]<part of module name> is cropped. So only part of the module name is showing, and, as you can see, the bracketing is wrong: The second closing bracket is missing (so that is not a mistake made by me). The name of my module is the link to the GitHub repository.
I have tried to manually replace the generic type T in Wrapper with Bar by creating the structure
type WrapperBar struct {
Foo int
Data Bar
}
If I now adapt the function signature to
func(contract *MyContract) DoSomething() *WrapperBar
it works just fine. Unfortunately, I am using the structure Wrapper several times with different
type instantiations. So although creating all the types manually would be a workaround, it is obviously not a very elegant one.
Is there another workaround so that I can still use my generic Wrapper structure?
I am currently using go in version 1.18 and fabric-contract-api-go in version v1.1.1.
The Go contract-api at this time doesn't support Go Generics, the only workarounds I can suggest are the one that you have tried noted in this question or to write your chaincode without using the contract API, there is an example here https://github.com/hyperledger/fabric-samples/tree/main/chaincode/marbles02/go of an implementation that doesn't use the contract-api.
Your implemention will have to do more work in your chaincode such as providing your own method dispatching, validating and unmarshalling the input data
You could raise an issue at https://github.com/hyperledger/fabric-contract-api-go and ideally also contribute a PR that addresses this issue as I can't say when or if this would ever be supported.

Interface redeclaration in Go in aws-sdk-v2: is it correct?

In aws-sdk-v2 library for Go, we have the following interfaces definitions:
type Retryer interface {
GetInitialToken() (releaseToken func(error) error)
}
type RetryerV2 interface {
Retryer
GetInitialToken() (releaseToken func(error) error)
}
(the code is here: https://github.com/aws/aws-sdk-go-v2/blob/main/aws/retryer.go)
This causes compilation error:
aws/retryer.go:81: GetInitialToken redeclared (compile)
Is this code correct or not? Is it possible to redeclare function in of interfaces?
How am I supposed to solve this problem?
Likely you are using an old version of Go. Overlapping method sets are allowed since Go 1.14, and the code compiles on the Go Playground.
Quoting from Go 1.14 release log:
Per the overlapping interfaces proposal, Go 1.14 now permits embedding of interfaces with overlapping method sets: methods from an embedded interface may have the same names and identical signatures as methods already present in the (embedding) interface. This solves problems that typically (but not exclusively) occur with diamond-shaped embedding graphs. Explicitly declared methods in an interface must remain unique, as before.
If you get a compile-time error for the code you posted, that suggests you're using a Go prior to 1.14. Urgently update! Please note that only the last 2 major versions are supported (currently 1.17 and 1.16). You using a version like 1.13 is a major risk!

Encapsulation of third party configuration structs

I am working on a Go project where I am utilizing some rather big third-party client libraries to communicate with some third-party REST apis. My intent is to decouple my internal code API from these specific dependencies.
Decoupling specific methods from these libraries in my code is straightforward as I only need a subset of the functionality and I am able to abstract the use cases. Therefore I am introducing a new type in my code which implements my specific use cases; the underlying implementation then relies on the third-party dependencies.
Where I have a problem to understand how to find a good decoupling are configuration structs. Usually, the client libraries I am using provide some functions of this form
createResourceA(options *ResourceAOptions) (*ResourceA, error)
createResourceB(options *ResourceBOptions) (*ResourceB, error)
where *ResourceA and *ResourceB are the server-side configurations of the corresponding resources after their creation.
The different options are rather big configuration structs for the resources with lots of fields, nested structs, and so on. In general, these configurations hold more options then needed in my application, but the overall overlap is in the end rather big.
As I want to avoid that my internal code has to import the specific dependencies to have access to the configuration structs I want to encapsulate these.
My current approach for encapsulation is to define my own configuration structs which I then use to configure the third party dependencies. To give a simple example:
import a "github.com/client-a"
// MyClient implements my use case functions
type MyClient struct{}
// MyConfiguration wraps more or less the configuration options
// provided by the client-a dependency
type MyConfiguration struct{
Strategy StrategyType
StrategyAOptions *StrategyAOptions
StrategyBOptions *StrategyBOptions
}
type StrategyType int
const (
StrategyA StrategyType = iota
StrategyB
)
type StrategyAOptions struct{}
type StrategyBOptions struct{}
func (c *MyClient) UseCaseA(options *MyConfiguration) error {
cfg := &a.Config{}
if (options.Strategy = StrategyA) {
cfg.TypeStrategy = a.TypeStrategyXY
}
...
a.CreateResourceA(cfg)
}
As the examples shows with this method I can encapsulate the third-party configuration structs, but I think this solution does not scale very well. I already encounter some examples where I am basically reimplementing types from the dependency in my code just to abstract the dependency away.
Here I am looking for maybe more sophisticated solutions and/or some insights if my approach is generally wrong.
Further research from me:
I looked into struct embedding and if that can help me. But, as the configurations hold non-trivial members, I end up importing the dependency in my calling code as well to fill the fields.
As the usual guideline seems to be Accept interfaces return structs I tried to find a good solution with this approach. But here I can end up with a rather big interfaces as well and in the go standard library configuration structs seem not to be used via interfaces. I was not able to find an explicit statement if hiding configurations behind interfaces is a good practice in Go.
To sum it up:
I would like to know how to abstract configuration structs from third-party libraries without ending up redefining the same data types in my code.
What about a very simple thing - redefining the struct types you need in your wrapper package?
I am very new to go, so this might be not the best way to proceed.
package myConfig
import a "github.com/client-a"
type aConfig a.Config
then you only need to import your myConfig package
import "myConfig"
// myConfig.aConfig is actually a.Config
myConfig.aConfig
Not really sure if this helps a lot since this is not real decoupling, but at least you will not need to import "github.com/client-a" in every place

Get names of structs that implement an interface or inherit a struct

Is it possible to get a slice of strings that represent the names of all types that implement an interface or inherit from a specific struct in a specific package using reflection?
After some research on the reflect package's doc, I don't think it's possible. That's not the way reflection work in go: the interfaces mechanism not beeing declarative (but duck-typed instead), there is no such list of types.
That said, you may have more luck using the ast package to parse your project, get the list of types, and check wheter or not they implement an interface, then write some code to give you the said slice. That would add a step to compilation, but could work like a charm.
AFAIK, you can't do this with reflect, since packages are kinda out of reflect's scope.
You can do this the same way godoc's static analysis works. That is, using code.google.com/p/go.tools/go/types to parse the package's source code and get the type info.
The go oracle can do this. https://godoc.org/code.google.com/p/go.tools/oracle
Here is the relevant section of the user manual.

How can I have a common test suite for multiple packages in go?

When I'm writing an interface, its often convenient to define my tests in the same package as the interface, and then define multiple packages that implement the interface set, eg.
package/
package/impl/x <-- Implementation X
package/impl/y <-- Implementation Y
Is there an easy way to run the same test suite (in this case, located in package/*_test.go) in the sub packages?
The best solution I've come up with so far is to add a test package:
package/tests/
Which implements the test suite, and a test in each of the implementations to run the tests, but this has two downsides:
1) The tests in package/tests are not in _test.go files, and end up being part of the actual library, documented by godoc, etc.
2) The tests in package/tests are run by a custom test runner, which has to basically duplicate all the functionality of go test to scan for go tests and run them.
Seems like a pretty tacky solution.
Is there is a better way of doing this?
I don't really dislike the idea to use a separate testing library. If you have an interface and you have generic tests for each interface, other people that implement that interface might like to use these tests as well.
You could create a package "package/test" that contains a function
// functions needed for each implementation to test it
type Tester struct {
func New() package.Interface
func (*package.Interface) Done()
// whatever you need. Leave nil if function does not apply
}
func TestInterface(t *testing.T, tester Tester)
Notice that the signature of TestInterface does not match to what go test expects. Now, for each package package/impl/x you add one file generic_test.go:
package x
import "testing"
import "package/test"
// run generic tests on this particular implementation
func TestInterface(t *testing.T) {
test.TestInterface(t,test.Tester{New:New})
}
Where New() is the constructor function of your implementation. The advantage with this scheme is that
Your tests are reusable for whoever implements your interface, even from other packages
It is immediately obvious that you run the generic test suite
The test cases are where the implementation is and not at another, obscure place
The code can be adapted easily if one implementation needs special initialization or similar stuff
It's go test compatible (big plus!)
Of course, in some cases you need a more complicated TestInterface function, but this is the basic idea.
If you share a piece of code for reuse by different packages then yes, it is a library by definition. Even when used only for testing from *_test.go files. It's no different from importing "testing" of "fmt" in the _test.go file. And having the API documented by godoc is a plus, not minus IMHO.
Maybe something gets mixed up here a bit:
If package a defines an interface only than there is no code to
test as interfaces in Go are implementation free.
So I assume the methods in your interface in package a
have constraints. E.g. in
interface Walker {
Walk(step int)
Tired() bool
}
you contract assumes that Tired returns true if more than
500 steps have been Walk'ed (and false otherwise)
and your test code checks these dependencies
(or assumption, contracts, invariants whatever you
name it).
If this is the case I would provide (in package a) an exported
function
func TestWalkerContract(w Walker) error {
w.Walk(100)
if w.Tired() { return errors.New("Tired after 100 steps") }
w.Walk(450)
if !w.Tired() { return errors.New("Not tired after 100+450 steps") }
}
Which documents the contract properly and can be used by packages
b and c with types implementing walker to test their implementations
in b_test.go and c_test.go. IMHO it is perfectly okay that these
function like TestWalkerContract are displayed by godoc.
P.S. More common than Walk and Tired might be an error state
which is kept and reported until cleared/reseted.

Resources