Dealing with dependency cycles in Golang
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
a initial commit with import cycle Mar 3, 2015
b initial commit with import cycle Mar 3, 2015
.gitignore Initial commit Mar 3, 2015
README.md updated README Mar 3, 2015
main.go initial commit with import cycle Mar 3, 2015

README.md

dep

Dealing with dependency cycles in Golang

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.

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 !!