Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 777
flooblebit Avatar asked Jun 22 '18 10:06

flooblebit


People also ask

How do I fix import cycle not allowed in test?

According to above explanation, put the tests within the same package will always got into the import cycle problem, and the solution is to move it to another package.

How do you stop import cycles in go?

To avoid the cyclic dependency, we must introduce an interface in a new package say x. This interface will have all the methods that are in struct A and are accessed by struct B.

What is cyclic import?

In simplest terms, a circular import occurs when module A tries to import and use an object from module B, while module B tries to import and use an object from module A. You can see it on on an example with simple modules a.py and b.py: # a.py snippet. print('First line of a.py') from package.

How does import work in go?

Importing simply means bringing the specified package from its source location to the destination code, wiz the main program. Import in Go is very important because it helps bring the packages which are super essential to run programs. This article covers various aspects of “Imports in Go”.


1 Answers

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

  • You're mixing concerns. Perhaps view shouldn't be accessing action.Register at all, or perhaps action shouldn't be responsible for changing the names of views (or both). This seems the most likely.
  • You're relying on a concretion where you should be relying on an interface and injecting a concretion. For example, rather than the view accessing action.Register directly, it could call a method on an interface type defined within view, and injected into the View object when it is constructed.
  • You need one or more additional, separate packages to hold the logic used by both the view and action packages, but which calls out to neither.

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.
like image 189
Adrian Avatar answered Sep 20 '22 16:09

Adrian