Wire generates Go constructor calls from dependency types
The October explanation of Wire shows how provider functions become ordinary initialization code, with missing dependencies detected during generation.
Passing dependencies to constructors works well until changing one service requires editing a long sequence of startup calls. The dependency relationships can remain simple while their manual assembly becomes repetitive. Robert van Gent's October explanation of Wire addresses that assembly work with a generator that produces ordinary Go initialization code.
Wire is already part of the Go Cloud launch described in July. Today's article explains its design in more detail. It should therefore be read as a technical introduction to an existing component of that project, rather than the date on which dependency injection first became available through Go Cloud.
I like the choice to leave constructors as ordinary functions. An application should remain understandable by reading the dependencies its functions request. Automating the repetitive calls is useful when it preserves that information in source a maintainer can inspect.
The constructor signature describes an edge
A provider in Wire is a Go function that produces a value from its arguments. Those arguments describe what must already exist before the provider can run. A constructor returning a store and taking a database therefore tells the generator that database construction has to precede store construction.
That relationship is already present in conventional dependency-passing code. Wire does not require the store to look up its database through an arbitrary registration name. Types connect providers to their inputs. The program's construction graph follows from the available functions and the target the injector promises to produce.
For example, a provider might expose this shape. The declaration is illustrative and omits its implementation.
func NewStore(db *sql.DB) (*Store, error)
The function cannot construct a store until it has a database. It may also fail, which gives the generated caller another responsibility. The generator must arrange the calls and propagate the constructor's failure. It cannot make a database connection succeed merely because the graph of types is complete.
This distinction separates structural completeness from operational success. Missing a provider is a problem in the available construction instructions. Failing to connect to a database is a result of executing those instructions. Wire moves the first class of problem out of startup execution while retaining ordinary error handling for the second.
The injector requests a completed value
An injector describes the result the generator should assemble. Its inputs provide values the caller already has, and its body supplies providers or provider sets to wire.Build. The article demonstrates that related providers can be grouped into a set. A set reduces repeated declarations where several injectors use the same construction choices.
That grouping should follow an application's actual dependencies. A provider set does not explain a domain concept merely because it contains related constructor names. The useful question remains which inputs the target needs and which functions supply them. A maintainer can inspect the signatures without first learning a second configuration language.
The October article deliberately starts with an incomplete injector. A database provider needs connection information that no provider supplies. Running the generator reports the missing type and the dependency that requires it. The fix can supply another provider or make the missing information an injector argument.
These are different ownership choices. A provider can establish a value from other available dependencies. An injector argument makes the caller responsible for supplying it. The generator can identify the gap, but the application designer still decides where that input belongs.
The generated function contains normal control flow
After the graph is complete, Wire emits a file containing calls in dependency order. The article's output creates the default configuration, constructs the database, and then constructs the store. Errors return through ordinary Go branches. There is no Wire dependency in the executed initialization code.
This affects debugging. A developer can inspect the generated function to see which constructor ran before another and where an error returned. The explanation does not depend on reproducing a container's reflective lookup at runtime. The generated artifact expresses the wiring in the same language as the constructors themselves.
Generation also limits which dependencies enter that function. Wire can identify the providers needed for the requested output and emit the corresponding imports. The article presents this as a way to avoid including unused dependencies through a runtime registration arrangement. That benefit follows from knowing the construction graph before executing the application.
There is still generated code to maintain as an artifact of the build process. Changing a constructor changes the input to generation. The generated function should then reflect that change before the program is treated as ready to build or run. This is a consequence of moving the wiring step into a generator, rather than a reason to pretend the step has disappeared.
Cloud portability makes the use case concrete
Go Cloud's portable bucket API gives an application the same bucket type across provider implementations. Its setup differs by provider. The July launch shows GCP credentials and an HTTP client for one path, while the AWS path creates an AWS session. Replacing the implementation changes the construction dependencies even when the application's write operation remains the same.
Wire addresses exactly that kind of change. A common application operation can retain its portable dependency while the injector changes which providers construct it. The generator recalculates the necessary sequence. Application logic does not need to acquire credentials itself merely because the implementation's setup became more involved.
I see the value in keeping these responsibilities visible. The portable API describes what application code uses, and the providers describe how a deployment obtains it. A generated function joins those decisions without requiring every caller to repeat their construction order.
The October explanation also identifies Wire as a general-purpose tool, despite its cloud use case. The relevant condition is a graph of typed constructors whose assembly has become tedious. Where manual setup is already short and obvious, the article's own small example shows that hand-written initialization remains straightforward. In a larger graph, the same ordinary constructor signatures can supply the generator with enough information to produce the calls and expose missing inputs before startup.