第三方配置结构的封装

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