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:
hypervisor
pcakge referencing virtualbox.VirtualBox
type virtualbox
package referencing hypervisor.MountPath
typeI 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?
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.
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
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.
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
// ....
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.