-1

I have my project structure looks like this:

Structure of code:

hypervisor
├── hypervisor.go
├── hyperv
│   └── hyperv.go
└── virtualbox
    ├── vbox.go
    └── vboxprops.go

Source code:

//hypervisor/hypervisor.go
package hypervisor

type Hypervisor interface {
    Start(vmName string) error

    ListMounts(vmName string) ([]MountPath, error)

    //....
}


type MountPath struct {
    HostPath  string
    GuestPath string
}


func detect() (Hypervisor, error) {
    return &virtualbox.Virtualbox{}, nil  // <<1 HERE
}

// ... other code

And have another (nested) package :

//hypervisor/virtualbox/vbox.go
package virtualbox

type Virtualbox struct {
}

func (*Virtualbox) Start(vmName string) error {
    return vboxManage("startvm", vmName, "--type", "headless").Run()
}

func (*Virtualbox) ListMounts(vmName string) ([]hypervisor.MountPath, error) { // <<2 HERE
    // ....
} 

// ... other code

And as seen, of course, such code leads to import cycle not allowed . because of:

  1. hypervisor pcakge referencing virtualbox.VirtualBox type
  2. virtualbox package referencing hypervisor.MountPath type

I know if I move the struct MounthPath to another package would solve the issue, but I don't think is the correct solution design-wise.

Any suggestion?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Muhammad Hewedy
  • 29,102
  • 44
  • 127
  • 219
  • 4
    Possible duplicate: [Registering packages in Go without cyclic dependency](https://stackoverflow.com/questions/29271440/registering-packages-in-go-without-cyclic-dependency/29272910#29272910) – icza Nov 20 '20 at 10:00
  • I went through it quickly and didn't notice that answer resides thier. Thanks you – Muhammad Hewedy Nov 20 '20 at 18:18

2 Answers2

0

One of easiest way I would do is to separate entities into entities package for example (in this case: the Hypervisor and Virtualbox struct are entities or whatever you want to call it).
This is most common design I think, so every struct that inner packages use will not cause cyclic deps.
Example of usage: all time package structs are on top package level. time.Time{}, time.Duration{}, etc. time.Duration does not sit on time/duration package.

Nikko Khresna
  • 1,024
  • 8
  • 10
0

Following suggestions from Dave Cheney to define interfaces by the caller will avoid cycle dependencies in most cases. But this will only solve flat data models. In your case, you have nested entities ie., HyperVisor has fucntion which returns MounthPath. We can model this in two ways

  1. Define MouthPath in separate package (like you suggested). In addition, defining the interface in the virtualbox package will help in long term to provide alternative implementation for Hypervisor.

  2. Let virtualbox define both Hypervisor and MounthPath as interface. One disadvantage is that the hypervisor implementing package use virtualbox.MouthPath interface to satisfy the interface when passed like below.

//hypervisor/hypervisor.go

package hypervisor

type Hypervisor struct{
     someField []virtualbox.MountPath
}

type MountPath struct { // this can be used as virtualbox.MountPath
    hostPath  string
    guestPath string
}

func (m *MountPath) HostPath() string { return m.hostPath }
func (m *MountPath) GuestPath() string { return m.guestPath }

func detect() (Hypervisor, error) {
    return &virtualbox.Virtualbox{}, nil  // <<1 HERE
}

And have another package (Need not be nested)

//hypervisor/virtualbox/vbox.go
package virtualbox

type Hypervisor interface {
    Start(vmName string) error

    ListMounts(vmName string) ([]MountPath, error)

    //....
} 

type MountPath interface {
        HostPath()  string
        GuestPath() string
}

type Virtualbox struct {}

func (*Virtualbox) Start(vmName string) error {
    return vboxManage("startvm", vmName, "--type", "headless").Run()
}

func (*Virtualbox) ListMounts(vmName string) ([]MountPath, error) { // <<2 HERE
    // ....
} 
IhtkaS
  • 1,314
  • 3
  • 15
  • 31