godependenciescircular-dependency

Fixing import cycle in Go


So I have this import cycle which I'm trying to solve. I have this following pattern:

view/
- view.go
action/
- action.go
- register.go

And the general idea is that actions are performed on a view, and are executed by the view:

// view.go
type View struct {
    Name string
}

// action.go
func ChangeName(v *view.View) {
    v.Name = "new name"
}

// register.go
const Register = map[string]func(v *view.View) {
    "ChangeName": ChangeName,
}

And then in view.go we invoke this:

func (v *View) doThings() {
    if action, exists := action.Register["ChangeName"]; exists {
        action(v)
    }
}

But this causes a cycle because View depends on the Action package, and vice versa. How can I solve this cycle? Is there a different way to approach this?


Solution

  • An import cycle indicates a fundamentally faulty design. Broadly speaking, you're looking at one of the following:

    Generally speaking, you want to architect an application so that you have three basic types of packages:

    1. Wholly self-contained packages, which reference no other first-party packages (they can of course reference the standard library or other third-party packages).
    2. Logic packages whose only internal dependencies are of type 1 above, i.e., wholly self-contained packages. These packages should not rely on each other or on those of type 3 below.
    3. "Wiring" packages, which mostly interact with the logic packages, and handle instantiation, initialization, configuration, and injection of dependencies. These can depend on any other package except for other type 3 packages. You should need very, very few packages of this type - often just one, main, but occasionally two or three for more complex applications.