Dealing With Import Cycles in Go
Mar 3, 2015 · 3 minute read · CommentsGo
Go doesn’t allow import cycles to occur. If there are any import cycles detected, it throws a compile time error. Generally import cycles are considered as a bad design.
I recently came across such a situation when I wrote a test case to test my models, my config package that returned a db connection was dependent on model and the controller created a connection of type gorp.DbMap and passed it to each call in the model. This works fine, but the issue was when I wrote a test case, I had to create one such connection and pass it to my model methods and since Go asks me to create the tests in the same package, it caused a import cycle.
Import cycles are caused when a package ‘a’ depends on ‘b’ and ‘b’ in turn depends on ‘a’. Following Go code illustrates the classic problem of import cycle, AKA dependency cycle.
package a
import (
"fmt"
"github.com/mantishK/dep/b"
)
type A struct {
}
func (a A) PrintA() {
fmt.Println(a)
}
func NewA() *A {
a := new(A)
return a
}
func RequireB() {
o := b.NewB()
o.PrintB()
}
package b
import (
"fmt"
"github.com/mantishK/dep/a"
)
type B struct {
}
func (b B) PrintB() {
fmt.Println(b)
}
func NewB() *B {
b := new(B)
return b
}
func RequireA() {
o := a.NewA()
o.PrintA()
}
This results in the following error during compile time:
import cycle not allowed
package github.com/mantishK/dep/a
imports github.com/mantishK/dep/b
imports github.com/mantishK/dep/a
How do we avoid this?
The problem:
A depends on B
B depends on A
In order to avoid the cyclic dependency, we must introduce an interface in a new package say i. This interface will have all the methods that are in struct A and are accessed by struct B.
package i
type Aprinter interface {
PrintA()
}
By doing so, we can make the package b to import i rather than a. Now, package b looks like this:
package b
import (
"fmt"
"github.com/mantishK/dep/i"
)
func RequireA(o i.Aprinter) {
o.PrintA()
}
This still doesn’t solve the problem completely, we still need an instance of struct A in the methods of struct B. To solve this we need another package, say c. Package c will import both a and b, it creates an instance of a and passes it to b.
package c
import (
"github.com/mantishK/dep/a"
"github.com/mantishK/dep/b"
)
func PrintC() {
o := a.NewA()
b.RequireA(o)
}
The cycle is broken, yet the functionality is achieved.
A depends on B
B depends on I
C depends on A and B
Problem solved !!
Check out this code on Github