di

package
v0.2.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2025 License: MIT Imports: 17 Imported by: 0

README

Dependency Injection Container

This package provides a robust, production-ready typed dependency injection (DI) container for Go applications.

Overview

The DI container supports:

  • Generic type-safe registrations
  • Automatic dependency resolution
  • Singleton and transient scopes
  • Circular dependency detection
  • Tagged/qualified injections
  • Thread-safe operations
  • Interface-to-implementation mapping

Core Concepts

Container

The Container is the main DI container that holds all registrations and handles dependency resolution.

Scopes
  • Singleton: Single shared instance throughout the application lifecycle
  • Transient: New instance created each time it's resolved

API Reference

Register[T]

Purpose: Registers a constructor function that creates instances of type T

When to use:

  • When you want the container to create instances for you
  • When the type has dependencies that need automatic injection
  • For concrete types that require initialization logic
  • Supports singleton/transient scopes

Signature:

func Register[T any](c *Container, constructor interface{}, scope Scope, tag ...string) error

Examples:

// Register a service with dependencies
Register[*user.UserService](c, func(repo repo.UserRepository, email email.EmailServiceInterface) *user.UserService {
    return user.NewUserService(repo, email)
}, Singleton)

// Register a repository with DB dependency
Register[repo.UserRepository](c, func(db *gorm.DB) repo.UserRepository {
    return dataRepo.NewGormUserRepository(db)
}, Singleton)

// Register with tag for multiple implementations
Register[Logger](c, func() Logger { return &ConsoleLogger{} }, Singleton, "console")
Provide[T]

Purpose: Provides an existing instance for interface type T

When to use:

  • When you have an already created instance (like external services)
  • For interface-to-implementation mapping
  • When the instance is created outside the container (e.g., from config)
  • Typically used for singleton instances that don't need recreation

Signature:

func Provide[T any](c *Container, impl T, tag ...string) error

Examples:

// Provide existing email service instance for the interface
Provide[email.EmailServiceInterface](c, emailSvc)

// Provide existing DB instance
Provide[*gorm.DB](c, gdb)

// Provide with tag
Provide[Logger](c, &FileLogger{}, "file")
Resolve[T]

Purpose: Resolves and returns an instance of type T

Signature:

func Resolve[T any](c *Container) (T, error)

Examples:

// Resolve a service
userSvc, err := Resolve[*user.UserService](c)

// Resolve with tag
logger, err := ResolveWithTag[Logger](c, "console")

// Resolve slice of implementations
loggers, err := Resolve[[]Logger](c)
MustResolve[T]

Purpose: Resolves T and panics on error

Signature:

func MustResolve[T any](c *Container) T

Key Differences: Register vs Provide

Aspect Register Provide
Input Constructor function Existing instance
Dependencies Auto-injected None (instance already has them)
Scope Configurable (Singleton/Transient) Always Singleton
Use Case Creating new instances Registering existing instances
Type Concrete types with factories Interfaces with implementations

Usage Patterns

Basic Setup
c := New()

// Register dependencies
Register[*gorm.DB](c, func() *gorm.DB { return gdb }, Singleton)
Register[repo.UserRepository](c, func(db *gorm.DB) repo.UserRepository {
    return dataRepo.NewGormUserRepository(db)
}, Singleton)

// Resolve
repo := MustResolve[repo.UserRepository](c)
Interface Mapping
// Register implementation
Register[*ConsoleLogger](c, func() *ConsoleLogger { return &ConsoleLogger{} }, Singleton)

// Map to interface
Provide[Logger](c, MustResolve[*ConsoleLogger](c))

// Resolve interface
logger := MustResolve[Logger](c)
Tagged Dependencies
// Register multiple implementations with tags
Register[PaymentProcessor](c, func() PaymentProcessor { return &StripeProcessor{} }, Singleton, "stripe")
Register[PaymentProcessor](c, func() PaymentProcessor { return &PayPalProcessor{} }, Singleton, "paypal")

// Resolve specific implementation
stripe := MustResolveWithTag[PaymentProcessor](c, "stripe")
Circular Dependency Detection

The container automatically detects circular dependencies:

// This would panic with "circular dependency detected"
Register[A](c, func(b B) A { return A{b} }, Singleton)
Register[B](c, func(a A) B { return B{a} }, Singleton)

Error Handling

All resolution operations return errors for:

  • Missing registrations
  • Circular dependencies
  • Type mismatches

Use MustResolve for cases where you're certain the dependency exists.

Thread Safety

The container is thread-safe and can be used concurrently from multiple goroutines.

Integration Example

// In main.go
c := di.New()

// Register infrastructure
di.Register[*gorm.DB](c, func() *gorm.DB { return gdb }, di.Singleton)
di.Provide[email.EmailServiceInterface](c, emailSvc)

// Register repositories
di.Register[repo.UserRepository](c, func(db *gorm.DB) repo.UserRepository {
    return dataRepo.NewGormUserRepository(db)
}, di.Singleton)

// Register services
di.Register[*user.UserService](c, func(repo repo.UserRepository, emailSvc email.EmailServiceInterface) *user.UserService {
    return user.NewUserService(repo, emailSvc)
}, di.Singleton)

// In handlers
userSvc := di.MustResolve[*user.UserService](c)

Documentation

Overview

Package di provides dependency injection setup for the application.

Package di provides an improved dependency injection container

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetUserRepository

func GetUserRepository() repo.UserRepository

GetUserRepository resolves the user repository from the container.

func GetUserService

func GetUserService() *user.UserService

GetUserService resolves the user service from the container.

func InitContainer

func InitContainer(cfg config.Config, gdb *gorm.DB) error

InitContainer initializes the DI container with all dependencies.

func IsRegistered

func IsRegistered[T any](c *Container, tag ...string) bool

IsRegistered reports whether T (and optional tag) has at least one registration.

func MustResolve

func MustResolve[T any](c *Container) T

func MustResolveWithTag

func MustResolveWithTag[T any](c *Container, tag string) T

func Provide

func Provide[T any](c *Container, impl T, tag ...string) error

Provide binds a concrete implementation value to an interface type T as a singleton. This helper intentionally supports interface T only.

func Register

func Register[T any](c *Container, constructor any, scope Scope, tag ...string) error

Register registers a constructor for type T with the given scope and optional tag. Patches applied:

  • Allow constructors that return (T) or (T, error).
  • Basic validation on constructor input parameters: forbid primitives unless explicitly registered.
  • Double-check closed state while holding c.mu to avoid Close/Register races.

func RegisterFactory

func RegisterFactory[T any](c *Container, factory func() T, scope Scope, tag ...string) error

RegisterFactory registers a typed factory function for T that will be called directly (no reflection) when resolving. This is useful for hot-path types. The factory must be a `func() T` (no error). If you need error-returning factories, use Register with a constructor that returns (T, error).

func Resolve

func Resolve[T any](c *Container) (T, error)

func ResolveWithTag

func ResolveWithTag[T any](c *Container, tag string) (T, error)

Types

type Container

type Container struct {
	// contains filtered or unexported fields
}
var DIContainer *Container

DIContainer is the global DI container singleton.

func New

func New() *Container

func (*Container) Clear

func (c *Container) Clear()

Clear removes all registrations, singletons and resolving state.

func (*Container) Close

func (c *Container) Close()

Close marks the container as closed and clears internal maps. It acquires c.mu so that Close and concurrent Register cannot interleave.

func (*Container) GetRegisteredTypes

func (c *Container) GetRegisteredTypes() []string

GetRegisteredTypes returns all registered types (with tags) for debugging.

type Scope

type Scope int
const (
	Singleton Scope = iota
	Transient
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL