Zero Dependencies In Go
Tim Bray created a badge for projects that have zero dependencies. A noble cause!
https://www.tbray.org/ongoing/When/202x/2024/09/04/0dependencies
Go makes zero dependencies reasonably easy compared to other languages, but that’s not to say you shouldn’t use any. In my day-to-day work I rely on a handful of tried and tested dependencies for a lot of my project work:
- Chi for routing, although I haven’t had the chance to assess the updates to the stdlib http package that came in Go 1.22 which may make its use unnecessary.
- sqlx which extends the
database/sql
package and adds a lot of helpful functionality. - validator which provides a comprehensive validation library but is overkill if all you need are some simple checks.
It doesn’t make sense to write this functionality from scratch and maintain it when these packages have lots
of community contributions, are actively maintained, and for libraries like Chi, have
zero dependencies themselves. How Go is generally written means you’re unlikely to have the considerable dependency graphs of other languages.
Even so, I’ve seen Go projects that have pretty significant go.sum
files, often containing multiple versions of the
same lib because the culture of the organisation was to import.
But that’s not to say zero dependency isn’t something to strive for. Every dependency removed or not installed in the first place, means fewer CVEs and, as Tim mentions in his post, more predictable performance.
Adding enforcement
One way to keep dependencies to a minimum is to enforce allowed dependencies using depguard, which is a linter available in golanglint-ci.
Here’s an example from a golanglint-ci config:
linters:
disable-all: true
enable:
- depguard
linters-settings:
depguard:
rules:
main:
allow:
- $gostd
- github.com/tomjowitt/gotidal
The allow lines are self-explanatory. This project allows imports from two sources: The standard library, and the
project module as defined in the go.mod
file.
module github.com/tomjowitt/gotidal
go 1.22.0
With this approach, you can fail builds if new dependencies creep in. Having an allow list has two benefits:
- It forces developers to think, “Do I need this?” before they add another exception.
- What has been added is very explicit during code review.
You can even store your lint file outside the repo as read-only and pull it into CI at runtime, ensuring no exceptions can be added without authorisation. This works really well in heavily audited, compliance-heavy environments where dependencies are scrutinised before being added.
Earlier this year, I decided to try to write a library that used Go best practices, including zero dependencies. It was surprisingly easy. You can see the results, including the zero dependency badge, here:
https://github.com/tomjowitt/gotidal
All hail the standard library!