dix is a lightweight dependency injection (DI) framework for Go, implemented using reflection. Inspired by dig, it aims to decouple component dependencies through automated dependency resolution and lifecycle management.
The core of dix manages dependency injection for addressable types, mainly including func, ptr (pointer), and interface.
Core features include:
- Constructor Injection: Register constructors via
Provide. - Struct/Field Injection: Fill struct fields via
Inject. - Advanced Type Support: Support for
Interface,Map,Slicewith automatic aggregation. - Cycle Detection: Built-in graph algorithm for circular dependency detection.
- Web Visualization: HTTP module for interactive dependency graph visualization.
- Method Injection: Support setter injection via
DixInjectprefix methods. - Namespace Grouping: Support grouping dependencies by namespace.
- Safe APIs:
TryProvide/TryInjectvariants that return errors instead of panicking.
The core of dix is a container struct named Dix, which maintains a registry of providers and a cache of instantiated objects.
dix strictly limits the types that can be managed as dependencies. The core principle is only addressable or reference types are supported. This design ensures stability of dependency relationships and controllability of object lifecycles.
- Pointer:
- The most common type, usually pointing to a struct instance (e.g.,
*Service). - Pointers guarantee singleton behavior throughout the application lifecycle with shared state.
- The most common type, usually pointing to a struct instance (e.g.,
- Interface:
- Supports binding concrete implementations to interface definitions (e.g.,
Serviceinterface implemented by*ServiceImpl). - Key to implementing the Dependency Inversion Principle (DIP).
- Supports binding concrete implementations to interface definitions (e.g.,
- Func:
- Functions are first-class citizens and can be injected as dependencies.
- Commonly used for factory functions, middleware, or callbacks.
Note: Basic types (e.g., int, string, bool) cannot be directly injected as dependencies; they must be wrapped in the above types (usually as struct fields).
type Dix struct {
// Configuration options
option Options
// Provider registry
// key: output type (reflect.Type)
// value: list of provider functions for this type
providers map[outputType][]*providerFn
// Object cache pool (singleton pattern)
// key: output type -> group -> value list
objects map[outputType]map[group][]value
// Initialization state flags to prevent duplicate Provider execution
initializer map[reflect.Value]bool
}- Register (Provide): User registers constructor -> Parse function signature (input/output) -> Store in
providerstable. - Invoke (Inject): User requests object injection -> Recursively find dependencies -> Execute Provider function -> Cache result -> Fill target.
The Provide method registers constructors into the container.
Provider function parameters declare the component's dependencies:
- Func / Ptr / Interface:
- Behavior: Declared as direct dependency.
- Resolution: Container finds matching instance from registered Providers.
- Struct:
- Behavior: Recursive Dependency Injection.
- Resolution: Framework recursively traverses all exported fields. If a field type is supported (Func/Ptr/Interface), the container automatically finds and injects the corresponding instance.
- Slice (
[]T):- Behavior: Declared as aggregate dependency (List).
- Resolution: Container finds all Providers that can provide type
Tand aggregates results from the default group into a slice.
- Map (
map[string]T):- Behavior: Declared as aggregate dependency (Map).
- Resolution: Container finds all Providers for type
T, using their returned Keys (default "default") to form a Map.
Provider function return values define what components it provides to the container.
Constraints: Provider function must return 1 or 2 values.
- 1 value: That value is the provided component.
- 2 values: Second value must be
errortype. Iferroris not nil, container stops initialization and reports error.
Supported types:
- Func / Ptr / Interface:
- Behavior: Register as standard Provider for that type.
- Slice (
[]T):- Behavior: Register as list Provider for type
T. - Effect: Allows one Provider to provide multiple
Tinstances at once.
- Behavior: Register as list Provider for type
- Map (
map[string]T):- Behavior: Register as map Provider for type
T. - Effect: Allows Provider to specify component Key.
- Behavior: Register as map Provider for type
- Struct:
- Behavior: Recursive Auto-Flattening.
- Effect: Framework recursively traverses all exported fields. Supported field types are registered as separate Providers.
Injection is driven by Inject or internal getValue, using Lazy Evaluation strategy.
Inject function supports injection into struct pointers or functions.
1. Struct Pointer
When passing &MyStruct{}, framework scans fields for injection:
- Func / Ptr / Interface: Find and inject corresponding type instance.
- Struct: Recursive Injection - framework continues injecting into nested struct fields.
- Slice / Map: Execute aggregation injection logic.
- Method Injection: Auto-scan and execute methods prefixed with
DixInject(Setter injection variant).
2. Function
When passing a function (e.g., dix.Inject(func(a *A, b *B){ ... })):
- Parameters are treated as dependencies.
- Resolution logic same as Provider input parameters (see 3.1.1).
- Commonly used for initialization or startup hooks.
When multiple Providers exist for the same type, dix uses grouping (Group/Key) mechanism. The underlying storage is map[group][]value, where group is a label (default "default").
Resolution strategies for different dependency declarations:
- Single Value (
T): Query default group, take last value. - List (
[]T): Query default group, take all values. - Map (
map[string]T): Query all groups, take last value per group. - Full Map (
map[string][]T): Query all groups, take all values per group.
To prevent infinite recursion, dix builds a dependency graph before or during injection.
- Algorithm: Depth-First Search (DFS).
- Implementation:
detectCyclefunction indixinternal/cycle-check.go. - Logic: Build
map[reflect.Type]map[reflect.Type]booladjacency list, traverse to find back edges. If cycle found, immediately report error with cycle path.
Uses logging system for error recording.
- Rich Context: Error messages include stack traces, Provider function names, parameter types for debugging.
- Panic Recovery: Uses
defer recoverwhen executing user code (Provider functions) to prevent application crashes. - Safe APIs:
TryProvideandTryInjectmethods return errors instead of panicking, suitable for scenarios where graceful error handling is preferred.
dix provides HTTP-based visualization through the dixhttp module:
- Web Interface: Modern UI built with Tailwind CSS + Alpine.js + vis-network
- Features:
- Fuzzy search for types and functions
- Package-based grouping with collapsible sidebar
- Bidirectional dependency tracking (upstream & downstream)
- Depth control (1-5 levels or all)
- Interactive graph with drag, zoom, and click
- RESTful API: JSON endpoints for programmatic access
/api/stats- Summary statistics/api/packages- Package list/api/dependencies- Full dependency data/api/type/{name}- Type-specific dependency chain
dixglobal package provides a global container instance for sharing across application parts.
dixcontext package provides functionality to store container instance in context for passing through request chains.
dixhttp package provides HTTP server for visualizing dependency graphs with interactive web interface. See dixhttp/README.md for details.
| File | Responsibility |
|---|---|
dix.go |
Public API wrapper with generics support (Inject[T], InjectT[T], Provide). |
dixinternal/api.go |
Core public API (New, Provide, TryProvide, Inject, TryInject). |
dixinternal/dix.go |
Core logic: newDix initialization, inject recursive flow, Provider registration. |
dixinternal/provider.go |
providerFn struct definition, encapsulates reflection call details. |
dixinternal/util.go |
Utility functions: reflection Map/Slice creation, output type handling, graph building. |
dixinternal/cycle-check.go |
Circular dependency detection logic. |
dixinternal/logger.go |
Logging system integration with structured output. |
dixinternal/option.go |
Configuration option pattern (e.g., WithValuesNull). |
dixglobal/ |
Global container module with singleton instance. |
dixcontext/ |
Context integration module for storing container in context. |
dixhttp/ |
HTTP visualization module with modern web interface. |
Important: The Dix container is NOT thread-safe by design. Users should not call Provide/Inject concurrently on the same container instance. This is a deliberate design choice for performance, as dependency injection typically happens during application initialization (single-threaded).
For concurrent scenarios, consider:
- Complete all
Providecalls before starting concurrent operations - Use separate container instances per goroutine
- Wrap container access with external synchronization if needed
dix is a feature-complete Go dependency injection container. It trades some runtime performance (through reflection) for great development flexibility - acceptable during initialization phase.
Design highlights:
- Full utilization of Go language features (multi-return for error handling, struct field tags)
- Elegant support for collection type injection
- Rich extension ecosystem (global container, context integration, web visualization)
- Safe API variants for graceful error handling
This makes dix a complete dependency injection solution for Go applications.