Wire: Go Dependency Injection Tool

repository·main·Indexed 12 days ago

https://github.com/google/wire

A compile-time code generation tool that automates dependency injection in Go. Wire uses function parameters to represent dependencies and generates explicit initialization code in `wire_gen.go` without relying on runtime reflection. It includes a CLI with commands such as `gen` for code generation, `check` for validation, `show` for inspecting provider sets, and `diff` for comparing generated content.

Tokens
7.3K
Snippets
29
Records
40
Agent score
91%

What's inside Wire

  1. What is Wire and how does it work?

    main

    Wire is a code generation tool for automated dependency injection in Go.

    Core Concepts:

    • Dependency Representation: Dependencies between components are represented as function parameters. This encourages explicit initialization and avoids the use of global variables.
    • No Runtime Overhead: Wire operates without runtime state or reflection. It generates standard Go code that performs the initialization, meaning the resulting code is clean, performant, and useful even if you were to perform the initialization by hand.
    • Code Generation: Instead of using a container at runtime, Wire analyzes your provider functions and generates the necessary 'wire' code to connect them.
  2. Maintain compatibility in library provider sets

    main

    When designing wire.NewSet for a library, follow these rules to avoid breaking downstream users' injectors:

    Safe changes (Backward Compatible):

    • Change which provider is used for a specific output, provided it doesn't introduce new inputs to the set.
    • Introduce a new output type, provided the type itself is brand new (to avoid conflicts with existing outputs in user injectors).

    Breaking changes (Avoid these):

    • Requiring a new input in the provider set.
    • Removing an output type from a provider set.
    • Adding an existing output type into the provider set.
    • Adding a provider for a type that users might already provide (e.g., adding io.Writer to a set).

    Best Practice: Prefer small provider sets. A common pattern is to include a single provider function and a wire.Bind to the interface it implements. Avoid bundling common dependencies (like *http.Client) in your library's set; instead, make them inputs to the set.

  3. Why explicit interface bindings are required

    main
    Wire requires you to explicitly declare that a type provides an interface type. This explicit binding prevents the dependency graph from breaking unexpectedly when a new type that implements the same interface is added to the graph. This design prioritizes explicit developer intent, which aligns with Go's philosophy.
  4. How providers and injectors work in Wire

    main

    Wire uses two main concepts to automate dependency injection:

    1. Providers: These are regular Go functions that return a specific type. For example, func NewGreeter(m Message) Greeter is a provider for the Greeter type.
    2. Injectors: These are functions that call wire.Build with a set of providers and return the target type. The injector's signature determines what dependencies are passed in from the outside.

    To prevent the injector code from being included in your final application binary, you should use a build constraint at the top of your wire.go file:

    //+build wireinject

    Note: A build constraint requires a blank, trailing line after it.

    // wire.go
    //+build wireinject
    
    func InitializeEvent() Event {
        wire.Build(NewEvent, NewGreeter, NewMessage)
        return Event{}
    }
  5. Avoid type conflicts by distinguishing common types

    main

    When injecting common types like string, int, or bool, you may encounter conflicts if multiple providers attempt to provide the same base type. To prevent this, wrap the common type in a new, domain-specific type.

    For example, instead of injecting a raw string for a connection string, define a custom type:

    type MySQLConnectionString string
  6. Why Wire forbids duplicate providers

    main
    Wire forbids including the same provider multiple times to maintain consistency with its core principle: specifying multiple providers for the same type is an error. Allowing duplicates would introduce complexity regarding which wire.Value calls are considered identical and could cause unexpected application breakage if a provider set changes and introduces a conflict with another set.
  7. Why Wire uses pseudo-functions for provider sets and injectors

    main
    Wire uses no-op function calls (pseudo-functions) instead of specially formatted comments to define directives. This approach ensures that standard Go tooling, such as gorename or guru, can recognize and correctly process references to identifiers used within Wire, maintaining seamless interoperability with the Go ecosystem.
  8. Core concepts: Providers and Injectors

    main

    Wire is built on two fundamental abstractions:

    1. Providers: Ordinary Go functions that produce a value. They can take dependencies as parameters and return errors.
    2. Injectors: Functions that define the dependency graph. You write the function signature (including parameters and return types), and Wire generates the function body by calling providers in the correct dependency order.

    To use injectors, you must include the // +build wireinject build tag in the file so the stub is not included in your final application binary.

    // +build wireinject
    
    package main
    
    import (
        "context"
        "github.com/google/wire"
        "example.com/foobarbaz"
    )
    
    func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
        wire.Build(foobarbaz.MegaSet)
        return foobarbaz.Baz{}, nil
    }
  9. Advanced features in Wire

    main

    Beyond basic provider initialization, Wire supports several advanced dependency injection patterns:

    • Provider Sets: Group multiple providers together into a single unit using sets.
    • Binding Interfaces: Map a concrete implementation to an interface so the injector can satisfy interface requirements.
    • Binding Values: Provide specific constant values or configuration objects directly into the dependency graph.
    • Cleanup Functions: Support for functions that handle resource teardown (e.g., closing database connections) during the initialization lifecycle.
  10. How Wire differs from reflection-based DI tools

    main
    Unlike dependency injection tools such as dig or facebookgo/inject which rely on runtime reflection, Wire is a code generator. This means the generated injector works without a runtime library, allowing for easier introspection of initialization and better compatibility with Go tooling like guru because the dependency graph is resolved at compile time.
  11. Handle errors in generated code

    main

    If your providers return an error (e.g., func NewEvent(g Greeter) (Event, error)), Wire automatically detects this. It will update the generated injector signature to return an error and insert the necessary error checking logic into the generated wire_gen.go file.

    Example Injector Signature:

    func InitializeEvent() (Event, error) {
        wire.Build(NewEvent, NewGreeter, NewMessage)
        return Event{}, nil
    }

    Generated Code Result:

    func InitializeEvent() (Event, error) {
        message := NewMessage()
        greeter := NewGreeter(message)
        event, err := NewEvent(greeter)
        if err != nil {
            return Event{}, err
        }
        return event, nil
    }
    // wire.go
    func InitializeEvent() (Event, error) {
        wire.Build(NewEvent, NewGreeter, NewMessage)
        return Event{}, nil
    }
  12. Generate Wire code

    main

    After defining your injectors, run the wire command in the package directory to generate the implementation in a wire_gen.go file.

    wire

    To update the generated code later, you can use the standard Go command:

    go generate
    wire