Introduction
“Mature tools should learn to write code themselves.” This article introduces Wire, a Go dependency injection tool, its usage, and various practical techniques accumulated from experience. When code reaches a certain scale, Wire can play a significant role in component decoupling, development efficiency, and maintainability, especially in large monolithic repositories.
Dependency Injection
As projects grow larger, dependencies in code increase: various database and middleware client connections, data access layer repository instances, and service instances in layered design…
For code maintainability, coupling between components should be avoided. The specific approach follows an important design principle: all dependencies should be passed to components during instance initialization. This is dependency injection (Dependency injection).
Dependency injection is a standard technique for producing flexible and loosely coupled code, by explicitly providing components with all of the dependencies they need to work.
– Go Official Blog
Here’s a simple example where all component instances Message, Greeter, and Event receive their dependencies during initialization:
| |
Introduction to Wire
When there are more and more component instances in a project, manually writing initialization code and maintaining dependencies between components becomes tedious, especially in large repositories. Therefore, the community has developed several dependency injection frameworks.
Besides Google’s Wire, there are Dig (Uber) and Inject (Facebook). Both Dig and Inject are based on Golang’s Reflection. This not only impacts performance but also makes the dependency injection mechanism opaque to users—very “black box.”
Clear is better than clever, Reflection is never clear.
— Rob Pike
In contrast, Wire is entirely based on code generation. During development, Wire automatically generates component instance initialization code. The generated code is human-readable, can be committed to the repository, and compiles normally. Therefore, Wire’s dependency injection is very transparent and doesn’t bring any performance overhead at runtime.
Getting Started
Here’s how to use Wire:
Step 1: Download and Install Wire
Download and install the wire command-line tool:
| |
Step 2: Create wire.go File
Before generating code, we first declare the dependencies and initialization order of each component. Create a wire.go file at the application entry point.
cmd/web/wire.go
| |
This file won’t participate in compilation; it just tells Wire about component dependencies and the expected generation results. In this file: we expect Wire to generate a CreateApp function that returns an App instance or error. All dependencies required for App instance initialization are provided by the ProviderSet provider list, which declares all potentially needed component acquisition/initialization methods and implicitly defines the dependency order between components.
Component acquisition/initialization methods are called “providers” in Wire
Some important notes:
- The file must start with
// +build wireinjectand a subsequent blank line, otherwise it will affect compilation - In this file, editors and IDEs may not provide code hints, but we’ll show how to solve this problem later
- The return values in
CreateApp(two nils) have no meaning; they’re just for Go syntax compatibility
Step 3: Generate Initialization Code
Execute wire ./... on the command line, and you’ll get the following automatically generated code file.
cmd/web/wire_gen.go
| |
Step 4: Use the Initialization Code
Wire has generated the real CreateApp initialization method for us, and now we can use it directly.
cmd/web/main.go
| |
Usage Tips
On-Demand Component Loading
Wire has an elegant feature: no matter how many providers are passed in wire.Build, Wire will only initialize component instances as actually needed. All unnecessary components won’t have corresponding initialization code generated.
Therefore, we can provide as many providers as possible and let Wire handle component selection. This way, whether we’re adding new components or deprecating old ones during development, we don’t need to modify the initialization code in wire.go.
For example, we can provide all instance constructors in the services layer.
pkg/services/wire.go
| |
In initialization, reference all potentially needed providers.
cmd/web/wire.go
| |
In subsequent development, if you need to reference new dependencies, just add them to the parameters. Wire will intelligently generate initialization code for required component instances based on actual needs.
| |
Even if Wire can’t find the required provider, it will report an error during compilation, not during runtime in production.
wire: cmd/api/wire.go:23:1: inject CreateApp: no provider found for *io.WriteCloser
Editor and IDE Configuration Support
Because the wire.go file includes this comment, Go will skip this file during compilation, but this also affects editor and IDE code hints. When editing the wire.go file, common editors and IDEs cannot properly provide code completion and error hints.
| |
But this problem is easy to solve. Find your IDE/editor’s Go environment configuration and add the parameter -tags=wireinject to Go Build Flags.

This configuration allows editors and IDEs to properly provide code completion and error hints for wire.go files, significantly improving the development experience.
Conflicts with Multiple Instances of the Same Type
This issue is relatively rare, but it’s easy to encounter in large projects.
Wire determines dependencies through provider parameters and return types. Sometimes, different instances of the same type might appear in the dependency network. When this happens, Wire cannot correctly determine dependencies and will report an error directly.
provider has multiple parameters of type ...
For example, in the following provider, the MySQL and PostgreSQL client instances have exactly the same type (both *gorm.DB), so Wire cannot correctly determine dependencies based on type and will report an error during code generation.
| |
The solution is relatively simple: just wrap the types.
| |
Then use ProviderService instead of NewService.
| |
Automatic Constructor Generation
As the number of structs serving as abstraction layers increases in a project, manually writing and maintaining struct constructors becomes tedious. If you add a pointer-type member to a struct but forget to update the constructor, it might even cause panics in production.
| |
Such tedious, repetitive, and error-prone work should be handled by automated tools. I recommend newc (meaning “New Constructor”), which can automatically generate and update struct constructor code.
Usage is very simple; just add this comment to the struct:
//go:generate go run github.com/Bin-Huang/[email protected]
For example:
| |
Then execute go generate ./... on the command line to get the constructor code:
constructor_gen.go
| |
This tool works great with Wire. When you need to use new dependencies, just add members to the struct. No need to manually update constructors or worry about initialization—all repetitive work is handled by automated tools (Wire and Newc). It works very well in real projects.
Of course, this tool may have some unconsidered situations, and I look forward to everyone’s feedback and suggestions.
Don’t repeat yourself “DRY”
Summary
Wire perfectly solves dependency injection problems, but it’s not a framework—it has no “magic” and isn’t a black box. It’s just a command-line tool that automatically generates initialization code for component instances based on actual needs. Then the problem is solved, with no additional complexity and no runtime performance overhead.
Wire shares the same qualities as Golang: simple, direct, and pragmatic. It truly deserves to be called Go’s most elegant dependency injection tool!
Keep it simple stupid “K.I.S.S”