Organizing code to have 2 FTP connections with some common methods and some specific ones

Hi,

I have 2 FTP connections ( FTP1, FTP2). Each connection has some common implementations ( connect(), download, etc. ), and specific implementations ( unzip() : files are in format .gzip in FTP1, files are in format .zip in FTP 2 for instance )

I would like to be able to call them like this:

func main() {
	var acqs []Acquisition
	ftp, err := NewFTP(os.Getenv("FTP_USER"), os.Getenv("FTP_PASSWORD"), os.Getenv("FTP_HOST"), os.Getenv("FTP_PORT"))
	if err != nil {
		panic(err)
	}
	ftp1 := &FTP1{*ftp}
	ftp2 := &FTP2{*ftp}


	acqs = append(acqs, ftp1, ftp2)
	for _, acq := range acqs {
		meters, err := acq.FetchMeters()
		if err != nil {
			log.Warn(acq.Name(), " got error :", err)
		}
		log.Info(meters)
	}
}

with :

type Acquisition interface {
	FetchMeters() ([]Meter, error)
	Name() string
}

type Meter struct {
	ID          string
	OperationID string
	Unit        string
}

type FTP struct {
	Username string
	Password string
	Url      string
	Port     string
	client   *goftp.FTP
}
type FTP1 struct {
	FTP
}
type FTP2 struct {
	FTP
}

func NewFTP(user, password, url, port string) (*FTP, error) {
	var err error
	ftp := &FTP{
		Username: user,
		Password: password,
		Url:      url,
		Port:     port,
	}
	if ftp.client, err = goftp.Connect(url + ":" + port); err != nil {
		return nil, err
	}
	if err = ftp.client.Login(user, password); err != nil {
		return nil, err
	}
	return ftp, nil
}

For all FTP connection, FetchMeters() will be:

func (ftp FTP) FetchMeters() ([]Meter, error) {
	log.Info(ftp.Name(), " is running")
	file := ftp.Download("")
	file = ftp.Unzip("")   // I have several implementation of Unzip
	log.Info(file)
	return nil, nil
}

where Download() is common to all FTP structs, but Unzip() is specific to ftp1, ftp2

Off course, in my case Unzip() doesn’t exists for FTP type, so it will fail.

What I should do is to put the receiver as an abstract type, an interface FTPAcq

type FTPAcq interface {
	Unzip(file string) string
}

func (ftp FTPAcq) FetchMeters() ([]Meter, error) {
    ...
}

But compiler is complaining.

I spent a lot of time figuring out how should I do, it seems easy but I can’t understand what I am missing.

Here is the full code in playground:

https://play.golang.org/p/6fsu5HIsP79

About what?

It says:

 invalid receiver type FTPAcq (FTPAcq is an interface type)

So you want to implement the method FetchMeters on the interface FTPAcq?

No, In FetchMeters, I would like to be able to have file = ftp.Unzip("") but Unzip() has different implementation on FTP1 and on FTP2.

Well, the compiler is probably complaining about this:

Here you try to implement the method FetchMeters on the interface FTPAcq which makes no sense. In Go, interfaces define method signatures, struct types implement them.

Remove

func (ftp FTPAcq) FetchMeters() ([]Meter, error) {
    ...
}

Regarding your question: Your interface FTPAcq defines one method Unzip(file string) string. If you want to implement this method differently for FTP1 and FTP1, do something like this:

func (ftp1 FTP1) Unzip(file string) string {
    // implementation 1
}

func (ftp2 FTP2) Unzip(file string) string {
    // implementation 2
}

Yes, but now, how do I call Unzip() without having to call ftp1.Unzip() or ftp2.Unzip()

I would like to have a method that doesn’t care which type of ftp it manages, and call ftp.Unzip(), and then that should redirect the code to the correct Unzip() implementation.

Maybe pass an instance of a type implementing FTP as an argument?

That is what I am trying to achieve, but I can’t do it :frowning:

No, you try to implement a method on an interface, that’s not the same as passing an argument. Maybe this?

func FetchMeters(ftp FTPAcq) ([]Meter, error)

I could do this:

func (acq FTPAcq) FetchMeters(ftp FTP) ([]Meter, error) {
	log.Info(ftp.Name(), " is running")
	file := Download("")
	file = acq.Unzip("") // I have several implementation of Unzip
	log.Info(file)
	return nil, nil
}

But now, meters, err := acq.FetchMeters() has not the same signature than the interface

It is important to mention that I have another type of Acquisition: API, I ommited it for simplicity, but so FetchMeters() will have 2 different implementations depending if it is API or FTP. Then I have several implementations of API ( API1, API2) and FTP (FTP1,FTP2)

I have done 100 differents ways, while writing, and cannot find a way that suit for all cases :frowning:

ftp.FetchMeters() and api.FetchMeters() must have 2 differents implementations. ftp would satisfy FTPAcq interface while api would satisfy APIAcq

type FTPAcq interface {
	Unzip(file string) string
}

type APIAcq interface {
	OtherMethod(file string) string
}

As you see, I’m a bit lost