Any good advice about how to avoid import cycle in Go?
Asked Answered
P

4

45

I'm working on a Go project for a month. The good thing is Go is really highly efficient. But after a month of development I've already got thousands lines of code and many packages. To avoid import cycle is a major issue for me that anytime I got a import cycle error, I have no idea where the problem may be at first time.

The Go compiler also only have very simple notice that always not good enough to locate issue quickly like: main.go:7:3: import cycle not allowed. It will only help you to know which file may cause the problem but nothing more deeply. Since import relationship just become more and more complex while code grows, I'm eager to know how to avoid import cycle more efficiently in Go. Any help is much appreciated.

Parasite answered 23/4, 2013 at 11:49 Comment(0)
L
59
go list -f '{{join .Deps "\n"}}' <import-path>

Will show import dependencies for package at <import-path> - or in current directory if <import-path> is left empty. Alternatively

go list -f '{{join .DepsErrors "\n"}}' <import-path>

hopefully shows some useful information in your case. See also the output of

go help list

for additional information about the go list tool.

Lace answered 23/4, 2013 at 12:15 Comment(2)
Definitely good to know, didn't even know about go list, actually.Limbate
Newer versions of go give you more information about where the import cycle is coming fromLlamas
L
44

To complement on jnml's answer (which helps "debug" circular references problems), you can use dependency inversion to break those cycles, coupled with dependency injection. For an application, I always try to follow the guidelines of the Clean Architecture - see here for a Go-specific example - and I find that Go's "non-declarative implementation" of interfaces (that is, you don't have to explicitly say type MyStruct struct implements IfceSomething) makes this very simple.

So, if you have packages A -> B -> C -> A, you create InterfaceA (some relevant name, obviously, more behaviour-related than package-related :) in package C and make it depend on this interface instead of on package A, and you make sure package A "implements" this interface.

Then you just have to provide a concrete implementation of A to C at some point (many possibilities here, I usually do this "glue" code in the main package that knows about all dependencies).

Limbate answered 23/4, 2013 at 17:27 Comment(0)
R
12

Since import relationship just become more and more complex while code grows, I'm eager to know how to avoid import cycle more efficiently in Go.

Another option is to visualize the dependencies in your project. This can be done with CLI tool godepgraph. You can install it with:

go get -u github.com/kisielk/godepgraph

And then use it for finding import cycles in your app with help of another CLI tool graphvis. Having this tools you can visualize package dependencies:

godepgraph -s path/to/my/package | dot -Tpng -o godepgraph.png
open ./godepgraph.png

to find cycles in my code: enter image description here

Razee answered 26/3, 2018 at 17:34 Comment(0)
P
0

As an aside to using the go list tool, which may confirm dependencies you already know about, from a architectural view you want to make sure that your dependency tree is deep enough that you can find out about cycles by building out sub-components. If there is a import cycle in a module then the cycle that exists should be clear. There should be enough modularity (tree depth) that moving these dependencies around works seamlessly.

Model -> Field  (Uses A) -- Needs to import "System"
Model -> System (Defines A) -- But needs to import "Field"
----------Move type A Struct A.go to top of module----------
----------This is what the Model Dir looks like now---------
Model -> A
Model -> Field
Model -> System

Now dependencies have been separated and the children can use A freely. This may not help with visualizing the dependencies or be a nice tool based solution, but once again, if you decouple your logic enough and build out the sub-components you should converge on the cycle pretty quickly. Otherwise if you're using a tree visualizer I would say that's a last resort and a result of poor design/not enough sub-components.

Propertius answered 3/1, 2021 at 20:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.