Don't couple your Go code to GitHub
Learn how to abstract GitHub dependencies in Go to keep your codebase flexible.
Go developers often reach for the official go‑github library the moment they need to talk to the GitHub API. The convenience of a typed client and the fact that the library lives in the same language ecosystem makes it tempting to import it directly into business logic. That convenience, however, creates a hard dependency on GitHub’s public API surface, the library’s versioning, and the network endpoint itself. The code becomes impossible to compile without internet access, difficult to test offline, and locked into GitHub as the only source control provider. In a micro‑service or CLI that should run against any Git host, that coupling is a hidden source of technical debt.
The clean way out is to treat the GitHub client as an external service and hide it behind a Go interface. Define an abstraction that captures only the operations your application needs, fetching pull requests, posting comments, creating releases, for example. Implement that interface with a thin wrapper around go‑github, and provide a mock implementation for unit tests. Dependency injection via constructors or a service locator lets the rest of the code depend on the interface, not the concrete library. Because the interface lives in your own module, you can swap the implementation for a self‑hosted GitLab client, a stub, or a future API version without touching the callers.
The price of this indirection is a modest amount of boilerplate: an extra package, a few interface definitions, and wrapper methods that forward calls. Maintaining the wrapper means you have to keep it in sync with any breaking changes in go‑github, and you may lose direct access to some niche API features unless you expose them through the interface. Nevertheless, the trade‑off pays off in testability, tests can run deterministically with a mock that returns canned data, and CI pipelines no longer need network credentials to compile. Moreover, the codebase becomes portable: a single change in the provider implementation lets you target GitHub Enterprise, GitLab, or any compatible API without a rewrite.
In practice, start by extracting a small `vcs` package that declares an interface like `type RepoService interface { ListPRs(ctx context.Context, owner, repo string) ([]PR, error) }`. Write a `githubRepoService` struct that embeds the go‑github client and satisfies the interface. Wire it up in `main` with something like `svc := NewGitHubService(github.NewClient(nil))` and pass `svc` to the components that need it. For tests, implement `RepoService` with an in‑memory map or a testify mock. If you ever need to support another host, drop in a new implementation and adjust the constructor. This pattern keeps the core logic free of external API noise and makes the codebase resilient to provider changes.
TakeawayWrap the GitHub client in an interface and inject it to eliminate direct coupling.