What are package dependencies and module dependencies?
A package dependency is another package that your Go package imports. When you import a package in Go, you create a dependency on that package. The Go compiler will not compile your package if it cannot find and compile the package you depend on.
On the other hand, a module dependency is a dependency on a module. A module is a collection of related Go packages that are versioned together. You declare your module dependencies in your go.mod
file. Your code may use one or more packages from your module dependencies.
Why are package dependencies important?
Understanding your package dependencies is essential because they:
- indicate the amount of internal coupling in your codebase
- help you understand the structure of your codebase
- help you avoid too many dependencies
- help you avoid circular dependencies
- help you optimize your build times
As your codebase grows, keeping track of package dependencies is vital to ensure that the codebase remains maintainable. Many developers import dependencies without considering the consequences. In modern IDE tools, they quickly click Import
in a pop-up to make the squiggly lines go away. In some cases, IDEs add imports without even asking the developer. However, code with many dependencies becomes coupled to other potentially unrelated code. This entanglement makes the codebase harder to understand, test, and maintain. For additional details, see the list of problems with a coupled architecture from our previous article.
What is an architectural test?
An architectural test is a test that makes sure your code follows the architectural rules that you have defined. Codebases tend to devolve into a Big Ball of Mud as time passes. Architectural tests are one way to keep your codebase clean.
In our example below, we will check to ensure that our Go package is NOT dependent on another package in our codebase. This is a common scenario when you want to refactor your codebase and remove a dependency or add a new package and want to ensure that it is not dependent on other parts of the codebase.
Find package dependencies using go list
go list
is a powerful tool that you can use to list information about Go packages. You can use the -deps
flag with go list
to find package dependencies. Here is an example:
go list -deps ./server/android...
The result is a list of all the direct and indirect package dependencies of the ./server/android
and its subpackages. To filter out standard library packages and sort the list, you can use the following command on macOS:
go list -deps ./server/android... | grep -E '^[^\/]*\.[^\/]*\/' | sort
The above regular expression looks for packages with a .
before the first /
in the package path. This regex filters out standard library packages. The sort
command sorts the list alphabetically.
To check if a package is dependent on another package, you can use the following command:
! (go list -deps ./server/android... | grep -q 'github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm')
The leading !
inverts the command’s exit status. If the package is dependent on the specified package, the command will return 1
; if it is not, the command will return 0
. You can use this command in your CI/CD pipelines to ensure that your package is not dependent on a specific package.
Find package dependencies using Go code
packages is a Go package that allows one to load, parse, type-check, and import Go packages. We will use the Load
function to get a list of Package
values. In addition, we will use Context.Import method from build package to recursively find dependencies.
Below is an example architecture test you can add to your test suite.
The above example is based on https://github.com/matthewmcnew/archtest. You can jump to the code example section of the video below for a full explanation.
A failing run of our architecture test will look like this:
=== RUN TestPackageDependencies
arch_test.go:41: Error: package dependency not allowed. Dependency chain:
github.com/fleetdm/fleet/v4/server/android/service
github.com/fleetdm/fleet/v4/server/fleet
github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm
--- FAIL: TestPackageDependencies (14.66s)
Find how a dependency is included in the build
In our article on analyzing Go build times, we show how to use the -debug-actiongraph
flag to find why a dependency is included in the build.
Further reading
- In the previous article, we discussed how to scale your codebase with evolutionary architecture.
- Before that, we explained the difference between Go modules and Go packages.
- We also covered common code refactorings in Go for readability and maintainability.
Watch how to find package dependencies of a Go package
Note: If you want to comment on this article, please do so on the YouTube video.