vessel

package module
v1.0.5 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 8 Imported by: 35

README

Vessel 🚢

Go Reference Go Report Card codecov

Vessel is a powerful, type-safe dependency injection container for Go, built as part of the Forge framework. It provides elegant service lifecycle management, flexible dependency resolution, and comprehensive testing support.

🆕 What's New

  • 🏗️ Constructor Injection - Uber dig-style constructor-based DI with automatic dependency resolution using ProvideConstructor
  • 🔑 Typed Service Keys - Strongly-typed service keys with ServiceKey[T] for compile-time safety and IDE autocomplete
  • 🪝 Middleware System - Hook into service resolution and lifecycle events for logging, metrics, and validation
  • 📚 Batch Registration - Register multiple services efficiently with RegisterServices() and typed variants
  • 🔎 Service Discovery - Query and filter services with Query(), FindByGroup(), and FindByLifecycle()
  • 📦 Enhanced Scopes - Scope context storage with SetScoped()/GetScoped() for request-specific data
  • 🚨 Sentinel Errors - Proper error handling with exported sentinel errors for errors.Is() checking

✨ Features

  • 🎯 Type-Safe Generics - Compile-time type safety with Go generics
  • 🏗️ Constructor Injection - Uber dig-style automatic dependency resolution
  • 🔑 Typed Service Keys - Strongly-typed service keys for compile-time safety
  • 🔄 Multiple Lifecycles - Singleton, Transient, and Scoped services
  • ⚡ Lazy Dependencies - Defer expensive service initialization
  • 🔗 Typed Injection - Automatic dependency resolution with type checking
  • 🚀 Service Lifecycle - Built-in Start/Stop/Health management
  • 🔍 Circular Detection - Automatic circular dependency detection
  • 🧵 Concurrency Safe - Thread-safe container operations
  • 📦 Request Scoping - Perfect for HTTP request-scoped services with context storage
  • 🎭 Interface Binding - Register implementations as interfaces
  • 🪝 Middleware Hooks - Intercept resolve, start, and lifecycle events
  • 🔎 Service Discovery - Query and filter services by criteria
  • 📚 Batch Registration - Register multiple services efficiently
  • 🧪 Test Friendly - Easy mocking and testing utilities

📦 Installation

go get github.com/xraph/vessel

🚀 Quick Start

package main

import (
    "context"
    "fmt"
    "github.com/xraph/vessel"
)

type Database struct {
    connectionString string
}

func (d *Database) Name() string { return "database" }
func (d *Database) Start(ctx context.Context) error {
    fmt.Println("Connecting to database...")
    return nil
}
func (d *Database) Stop(ctx context.Context) error {
    fmt.Println("Closing database connection...")
    return nil
}

type UserService struct {
    db *Database
}

func main() {
    // Create a new container
    c := vessel.New()
    
    // Register services
    vessel.RegisterSingleton(c, "database", func(c vessel.Vessel) (*Database, error) {
        return &Database{connectionString: "postgres://..."}, nil
    })
    
    vessel.RegisterSingleton(c, "userService", func(c vessel.Vessel) (*UserService, error) {
        db := vessel.Must[*Database](c, "database")
        return &UserService{db: db}, nil
    })
    
    // Start all services
    ctx := context.Background()
    if err := c.Start(ctx); err != nil {
        panic(err)
    }
    defer c.Stop(ctx)
    
    // Resolve and use services
    userService := vessel.Must[*UserService](c, "userService")
    fmt.Printf("User service ready: %v\n", userService)
}

📖 Core Concepts

Service Lifecycles

Vessel supports three lifecycle types:

🔹 Singleton (Default)

Created once and shared across the entire application.

vessel.RegisterSingleton(c, "config", func(c vessel.Vessel) (*Config, error) {
    return LoadConfig(), nil
})
🔹 Transient

Created new every time it's resolved.

vessel.RegisterTransient(c, "request", func(c vessel.Vessel) (*Request, error) {
    return &Request{ID: uuid.New()}, nil
})
🔹 Scoped

Created once per scope, perfect for HTTP requests.

vessel.RegisterScoped(c, "session", func(c vessel.Vessel) (*Session, error) {
    return &Session{StartTime: time.Now()}, nil
})

// In HTTP handler
scope := c.BeginScope()
defer scope.End()

session, _ := vessel.ResolveScope[*Session](scope, "session")
🔹 Enhanced Scope Features

Scopes now support context storage for request-specific data:

scope := c.BeginScope()
defer scope.End()

// Store request-specific context
vessel.SetScoped(scope, "requestID", "abc-123")
vessel.SetScoped(scope, "user", currentUser)

// Retrieve typed values
requestID, ok := vessel.GetScoped[string](scope, "requestID")
user, ok := vessel.GetScoped[*User](scope, "user")

// Check scope status
if !scope.(*vessel.Scope).IsEnded() {
    // Scope is still active
}

// List services resolved in this scope
services := scope.(*vessel.Scope).Services()

🔑 Typed Service Keys

Use strongly-typed service keys for compile-time safety and IDE autocomplete:

// Define typed service keys
var (
    DatabaseKey    = vessel.NewServiceKey[*Database]("database")
    UserServiceKey = vessel.NewServiceKey[*UserService]("userService")
    LoggerKey      = vessel.NewServiceKey[Logger]("logger")
)

// Register with type safety
vessel.RegisterWithKey(c, DatabaseKey, func(c vessel.Vessel) (*Database, error) {
    return &Database{}, nil
}, vessel.Singleton())

vessel.RegisterWithKey(c, UserServiceKey, func(c vessel.Vessel) (*UserService, error) {
    db := vessel.MustWithKey(c, DatabaseKey) // Type-safe!
    return &UserService{db: db}, nil
})

// Resolve with full type safety and autocomplete
db, err := vessel.ResolveWithKey(c, DatabaseKey)
// db is *Database, no type assertion needed!

// Or use Must variant
userService := vessel.MustWithKey(c, UserServiceKey)

// Check if service exists
if vessel.HasKey(c, DatabaseKey) {
    // Service is registered
}

🏗️ Constructor Injection (Dig-Style)

Vessel supports Uber dig-style constructor-based dependency injection with automatic resolution:

// Simple constructor - dependencies are automatically resolved by type
type Database struct{}
type Logger struct{}

type UserService struct {
    db  *Database
    log *Logger
}

func NewDatabase() *Database {
    return &Database{}
}

func NewLogger() *Logger {
    return &Logger{}
}

func NewUserService(db *Database, log *Logger) *UserService {
    return &UserService{db: db, log: log}
}

c := vessel.New()

// Register constructors - dependencies are automatically resolved
vessel.ProvideConstructor(c, NewDatabase)
vessel.ProvideConstructor(c, NewLogger)
vessel.ProvideConstructor(c, NewUserService)

// Resolve by type
userService, err := vessel.InjectType[*UserService](c)
Constructor Options
// Named services - explicit primary name
vessel.ProvideConstructor(c, NewPrimaryDB, vessel.WithName("primary"))
vessel.ProvideConstructor(c, NewSecondaryDB, vessel.WithName("secondary"))

// Resolve named services
primary, _ := vessel.InjectNamed[*Database](c, "primary")
secondary, _ := vessel.InjectNamed[*Database](c, "secondary")

// Service aliases - register by TYPE with named aliases (recommended pattern)
// Primary access is by type, aliases provide named variants
vessel.ProvideConstructor(c, NewDatabaseManager, 
    vessel.WithAliases("manager", "db-manager"))

// Can resolve by type (unnamed):
mgr1, _ := vessel.InjectType[*DatabaseManager](c)
// Or by any alias:
mgr2, _ := vessel.InjectNamed[*DatabaseManager](c, "manager")
mgr3, _ := vessel.InjectNamed[*DatabaseManager](c, "db-manager")
// mgr1 == mgr2 == mgr3 (same singleton instance)

// Alternative: Named primary with unnamed alias
vessel.ProvideConstructor(c, NewCache, 
    vessel.WithName("cache"),
    vessel.WithAliases(""))  // "" = also accessible without name

// Check existence
if vessel.HasType[*Database](c) { /* ... */ }
if vessel.HasTypeNamed[*Database](c, "primary") { /* ... */ }

// Lifecycle options
vessel.ProvideConstructor(c, NewDB, vessel.AsSingleton())  // default
vessel.ProvideConstructor(c, NewReq, vessel.AsTransient())
vessel.ProvideConstructor(c, NewSession, vessel.AsScoped())

// Eager vs Lazy instantiation
vessel.ProvideConstructor(c, NewCache)                     // lazy (default): created on first use
vessel.ProvideConstructor(c, NewDatabase, vessel.WithEager())  // eager: created immediately, fails fast
Eager vs Lazy Instantiation

By default, services are lazy - they're created only when first requested. Use WithEager() for immediate instantiation:

// LAZY (default): Constructor called on first InjectType/InjectNamed
vessel.ProvideConstructor(c, NewCache)
// At this point, NewCache has NOT been called yet
db, _ := vessel.InjectType[*Cache](c)  // ← NewCache called here

// EAGER: Constructor called immediately during ProvideConstructor
vessel.ProvideConstructor(c, NewDatabase, vessel.WithEager())
// At this point, NewDatabase HAS been called and instance is cached
// If constructor fails, ProvideConstructor returns error immediately

When to use WithEager():

  • Fail-fast: Catch construction errors at startup, not during request handling
  • Pre-initialize: Open database connections, warm caches before serving requests
  • Start services: HTTP servers, background workers that should start immediately
  • Validate config: Ensure all required configuration is valid at startup

When to use lazy (default):

  • Fast startup: Only create services that are actually used
  • Optional features: Services that may not be needed in all code paths
  • Memory efficiency: Don't create expensive services that won't be used
  • Testing: Create only services needed for specific test cases

Example: Database with eager initialization

// Register database with eager instantiation for fail-fast behavior
err := vessel.ProvideConstructor(c, func(config *Config) (*Database, error) {
    db, err := sql.Open(config.Driver, config.DSN)
    if err != nil {
        return nil, fmt.Errorf("failed to open database: %w", err)
    }
    // Test connection immediately
    if err := db.Ping(); err != nil {
        return nil, fmt.Errorf("failed to ping database: %w", err)
    }
    return db, nil
}, vessel.WithEager())

if err != nil {
    // Connection error caught at startup, before serving requests
    log.Fatal("Failed to initialize database:", err)
}

// Database is already connected and ready to use
// All subsequent InjectType calls return the cached, connected instance
Value Groups

Collect multiple implementations of the same type:

// Register multiple handlers in a group
vessel.ProvideConstructor(c, NewAuthHandler, vessel.AsGroup("handlers"))
vessel.ProvideConstructor(c, NewLoggingHandler, vessel.AsGroup("handlers"))
vessel.ProvideConstructor(c, NewMetricsHandler, vessel.AsGroup("handlers"))

// Inject all handlers as a slice
handlers, err := vessel.InjectGroup[Handler](c, "handlers")
// handlers is []Handler containing all three handlers
Interface Registration

Register concrete types as interfaces:

type Writer interface {
    Write([]byte) error
}

type FileWriter struct{}
func (f *FileWriter) Write(b []byte) error { return nil }

func NewFileWriter() *FileWriter {
    return &FileWriter{}
}

// Register *FileWriter as Writer interface
vessel.ProvideConstructor(c, NewFileWriter, vessel.As(new(Writer)))

// Resolve by interface type
writer, err := vessel.InjectType[Writer](c)
In/Out Parameter Objects (dig-style)

For constructors with many dependencies, use In and Out structs:

// Embed vessel.In for parameter objects
type ServiceParams struct {
    vessel.In
    
    DB       *Database           // Required dependency
    Logger   *Logger             // Required dependency
    Cache    *Cache `optional:"true"` // Optional, nil if not registered
    Primary  *Database `name:"primary"` // Named dependency
}

func NewService(p ServiceParams) *Service {
    return &Service{
        db:      p.DB,
        logger:  p.Logger,
        cache:   p.Cache,
        primary: p.Primary,
    }
}

vessel.ProvideConstructor(c, NewService)

// Embed vessel.Out for result objects (multiple results)
type ServiceResults struct {
    vessel.Out
    
    API  *APIHandler
    Web  *WebHandler
    GRPC *GRPCHandler
}

func NewHandlers(db *Database) ServiceResults {
    return ServiceResults{
        API:  &APIHandler{db: db},
        Web:  &WebHandler{db: db},
        GRPC: &GRPCHandler{db: db},
    }
}

// All three handlers are registered and resolvable
vessel.ProvideConstructor(c, NewHandlers)
api, _ := vessel.InjectType[*APIHandler](c)
web, _ := vessel.InjectType[*WebHandler](c)
Error Handling

Constructors can return errors:

func NewDatabase(cfg *Config) (*Database, error) {
    if cfg.ConnectionString == "" {
        return nil, errors.New("connection string required")
    }
    return &Database{conn: cfg.ConnectionString}, nil
}

// Error is returned when resolving
db, err := vessel.InjectType[*Database](c)
if err != nil {
    // Handle constructor error
}
Must Variants (Panic on Error)
// Panic if service not found or resolution fails
db := vessel.MustInjectType[*Database](c)
primary := vessel.MustInjectNamed[*Database](c, "primary")
handlers := vessel.MustInjectGroup[Handler](c, "handlers")
Circular Dependency Detection

Vessel automatically detects circular dependencies:

func NewA(b *B) *A { return &A{b: b} }
func NewB(a *A) *B { return &B{a: a} } // Circular!

vessel.ProvideConstructor(c, NewA)
vessel.ProvideConstructor(c, NewB)

_, err := vessel.InjectType[*A](c)
// Error: circular dependency detected: *A -> *B -> *A

🪝 Middleware & Hooks

Intercept and observe service resolution and lifecycle events:

// Create logging middleware
loggingMiddleware := &vessel.FuncMiddleware{
    BeforeResolveFunc: func(ctx context.Context, name string) error {
        log.Printf("Resolving service: %s", name)
        return nil
    },
    AfterResolveFunc: func(ctx context.Context, name string, service any, err error) error {
        if err != nil {
            log.Printf("Failed to resolve %s: %v", name, err)
        } else {
            log.Printf("Successfully resolved %s", name)
        }
        return nil
    },
    BeforeStartFunc: func(ctx context.Context, name string) error {
        log.Printf("Starting service: %s", name)
        return nil
    },
    AfterStartFunc: func(ctx context.Context, name string, err error) error {
        if err != nil {
            log.Printf("Failed to start %s: %v", name, err)
        }
        return nil
    },
}

// Register middleware
c.(*vessel.ContainerImpl).Use(loggingMiddleware)

// Create custom middleware
type MetricsMiddleware struct {
    metrics *Metrics
}

func (m *MetricsMiddleware) BeforeResolve(ctx context.Context, name string) error {
    m.metrics.IncrementResolveCount(name)
    return nil
}

func (m *MetricsMiddleware) AfterResolve(ctx context.Context, name string, service any, err error) error {
    if err != nil {
        m.metrics.IncrementResolveError(name)
    }
    return nil
}

// Implement other methods...

📚 Batch Registration

Register multiple services efficiently:

// Batch register with untyped factories
err := vessel.RegisterServices(c,
    vessel.Service("database", NewDatabase, vessel.Singleton()),
    vessel.Service("cache", NewCache, vessel.Singleton()),
    vessel.Service("logger", NewLogger, vessel.Singleton(), vessel.WithGroup("core")),
)

// Batch register with type safety
err := vessel.RegisterTypedServices(c,
    vessel.TypedService("db", NewDatabase, vessel.Singleton()),
    vessel.TypedService("cache", NewCache, vessel.Singleton()),
)

// Batch register with service keys
err := vessel.RegisterKeyedServices(c,
    vessel.KeyedService(DatabaseKey, NewDatabase, vessel.Singleton()),
    vessel.KeyedService(CacheKey, NewCache, vessel.Singleton()),
    vessel.KeyedService(LoggerKey, NewLogger, vessel.Singleton()),
)

🔎 Service Discovery & Querying

Query and filter services by various criteria:

// Find all singleton services
singletons := vessel.FindByLifecycle(c, "singleton")

// Find all services in a group
apiHandlers := vessel.FindByGroup(c, "api-handlers")

// Find started services
started := vessel.FindStarted(c)

// Find not started services
notStarted := vessel.FindNotStarted(c)

// Complex queries
started := true
results := vessel.Query(c, vessel.ServiceQuery{
    Lifecycle: "singleton",
    Group:     "api",
    Metadata: map[string]string{
        "version": "2.0",
        "env":     "production",
    },
    Started: &started,
})

// Get just the names
names := vessel.QueryNames(c, vessel.ServiceQuery{
    Group: "background-workers",
})

for _, info := range results {
    fmt.Printf("Found: %s (%s)\n", info.Name, info.Lifecycle)
}

🎯 Type-Safe Resolution

Generic Resolve
// Type-safe resolution with error handling
db, err := vessel.Resolve[*Database](c, "database")
if err != nil {
    log.Fatal(err)
}

// Panic on error (use during startup)
db := vessel.Must[*Database](c, "database")
Resolve with Service Start

Ensure a service and its dependencies are started before use:

// Resolves and starts the service if it implements di.Service
db, err := vessel.ResolveReady[*Database](ctx, c, "database")

💉 Typed Dependency Injection

Use Provide for automatic dependency injection with type safety:

// Define dependencies with Inject
vessel.Provide[*UserService](c, "userService",
    vessel.Inject[*Database]("database"),
    vessel.Inject[*Logger]("logger"),
    func(db *Database, log *Logger) (*UserService, error) {
        return &UserService{
            db:     db,
            logger: log,
        }, nil
    },
)

// Or use lifecycle-specific helpers
vessel.RegisterSingletonWith[*UserService](c, "userService",
    vessel.Inject[*Database]("database"),
    func(db *Database) (*UserService, error) {
        return &UserService{db: db}, nil
    },
)

⚡ Lazy Dependencies

Break circular dependencies or defer expensive initialization:

type EmailService struct {
    cache *vessel.Lazy[*Cache]
}

vessel.RegisterSingleton(c, "emailService", func(c vessel.Vessel) (*EmailService, error) {
    return &EmailService{
        cache: vessel.NewLazy[*Cache](c, "cache"),
    }, nil
})

// Later, when needed:
func (s *EmailService) SendEmail(to string, body string) error {
    cache, err := s.cache.Get() // Resolved on first access
    if err != nil {
        return err
    }
    // Use cache...
}
Optional Lazy Dependencies
vessel.Provide[*Service](c, "service",
    vessel.LazyInject[*Cache]("cache"),
    func(cache *vessel.Lazy[*Cache]) (*Service, error) {
        return &Service{cache: cache}, nil
    },
)

// With optional dependencies
vessel.Provide[*Service](c, "service",
    vessel.OptionalInject[*Cache]("cache"),
    func(cache *Cache) (*Service, error) {
        // cache will be nil if not registered
        return &Service{cache: cache}, nil
    },
)

🔧 Service Lifecycle Management

Implement the di.Service interface for automatic lifecycle management:

type DatabaseService struct {
    conn *sql.DB
}

func (d *DatabaseService) Name() string {
    return "database"
}

func (d *DatabaseService) Start(ctx context.Context) error {
    conn, err := sql.Open("postgres", "...")
    if err != nil {
        return err
    }
    d.conn = conn
    return d.conn.PingContext(ctx)
}

func (d *DatabaseService) Stop(ctx context.Context) error {
    return d.conn.Close()
}

// Optional: Health checks
func (d *DatabaseService) Health(ctx context.Context) error {
    return d.conn.PingContext(ctx)
}

// Register and manage lifecycle
c := vessel.New()
vessel.RegisterSingleton(c, "database", func(c vessel.Vessel) (*DatabaseService, error) {
    return &DatabaseService{}, nil
})

// Start all services in dependency order
ctx := context.Background()
c.Start(ctx)

// Check health of all services
c.Health(ctx)

// Stop all services in reverse order
c.Stop(ctx)

🎭 Interface Registration

Register implementations as interfaces:

type Logger interface {
    Log(msg string)
}

type ConsoleLogger struct{}

func (c *ConsoleLogger) Log(msg string) {
    fmt.Println(msg)
}

// Register implementation as interface
vessel.RegisterSingletonInterface[Logger, *ConsoleLogger](c, "logger",
    func(c vessel.Vessel) (*ConsoleLogger, error) {
        return &ConsoleLogger{}, nil
    },
)

// Resolve as interface
logger := vessel.Must[Logger](c, "logger")
logger.Log("Hello, World!")

📦 Scoped Services for HTTP Requests

Perfect for request-scoped resources with context storage:

func httpHandler(c vessel.Vessel) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Create a new scope for this request
        scope := c.BeginScope()
        defer scope.End() // Cleanup when done
        
        // Store request-specific context
        vessel.SetScoped(scope, "requestID", r.Header.Get("X-Request-ID"))
        vessel.SetScoped(scope, "userID", getUserIDFromToken(r))
        
        // Resolve scoped services
        session, _ := vessel.ResolveScope[*Session](scope, "session")
        userCtx, _ := vessel.ResolveScope[*UserContext](scope, "userContext")
        
        // Retrieve context data in services
        requestID, _ := vessel.GetScoped[string](scope, "requestID")
        log.Printf("Handling request: %s", requestID)
        
        // Use services...
        
        // Services are automatically cleaned up when scope ends
    }
}

🔍 Dependency Inspection

// Check if service is registered
if c.Has("database") {
    // Service exists
}

// Check if service has been started
if c.IsStarted("database") {
    // Service is running
}

// Get service information
info := c.Inspect("database")
fmt.Printf("Service: %s, Type: %s, Started: %v\n", 
    info.Name, info.Type, info.Started)

// List all registered services
services := c.Services()
for _, name := range services {
    fmt.Println(name)
}

🧪 Testing Support

Vessel makes testing easy with mock services:

func TestUserService(t *testing.T) {
    c := vessel.New()
    
    // Register mock database
    vessel.RegisterSingleton(c, "database", func(c vessel.Vessel) (*MockDatabase, error) {
        return &MockDatabase{
            users: map[string]*User{
                "1": {ID: "1", Name: "Test User"},
            },
        }, nil
    })
    
    vessel.RegisterSingleton(c, "userService", func(c vessel.Vessel) (*UserService, error) {
        db := vessel.Must[*MockDatabase](c, "database")
        return &UserService{db: db}, nil
    })
    
    // Test with mocked dependencies
    service := vessel.Must[*UserService](c, "userService")
    user, err := service.GetUser("1")
    assert.NoError(t, err)
    assert.Equal(t, "Test User", user.Name)
}

🔗 Dependency Declaration

Declare dependencies explicitly for better documentation and validation:

// With dependency tracking
c.Register("userService", func(c vessel.Vessel) (any, error) {
    return &UserService{}, nil
}, vessel.WithDependencies("database", "logger"))

// Dependencies are validated at registration time
// and used for proper startup order

🎨 Advanced Patterns

Factory Pattern with Dependencies
vessel.Provide[*HTTPClient](c, "httpClient",
    vessel.Inject[*Config]("config"),
    vessel.Inject[*Logger]("logger"),
    func(cfg *Config, log *Logger) (*HTTPClient, error) {
        return &HTTPClient{
            timeout: cfg.HTTPTimeout,
            logger:  log,
        }, nil
    },
)
Value Registration

Register pre-built instances:

config := &Config{Port: 8080}
vessel.RegisterValue(c, "config", config)
Grouped Services

Register multiple services in the same group for discovery:

// Register services with groups
vessel.RegisterSingleton(c, "handler1", ..., vessel.WithGroup("handlers"))
vessel.RegisterSingleton(c, "handler2", ..., vessel.WithGroup("handlers"))
vessel.RegisterSingleton(c, "handler3", ..., vessel.WithGroup("handlers"))

// Discover services by group
handlers := vessel.FindByGroup(c, "handlers")
for _, info := range handlers {
    fmt.Printf("Found handler: %s\n", info.Name)
}

// Query services with metadata
vessel.RegisterSingleton(c, "worker1", ..., 
    vessel.WithGroup("workers"),
    vessel.WithDIMetadata("priority", "high"),
)

highPriorityWorkers := vessel.Query(c, vessel.ServiceQuery{
    Group: "workers",
    Metadata: map[string]string{"priority": "high"},
})

🚨 Error Handling

Vessel provides structured errors with sentinel values for easy checking:

service, err := vessel.Resolve[*Service](c, "unknown")
if err != nil {
    // Check with errors.Is for sentinel errors
    if errors.Is(err, vessel.ErrServiceNotFoundSentinel) {
        log.Println("Service not registered")
    }
    
    if errors.Is(err, vessel.ErrCircularDependencySentinel) {
        log.Println("Circular dependency detected")
    }
    
    if errors.Is(err, vessel.ErrScopeEnded) {
        log.Println("Scope has ended")
    }
    
    if errors.Is(err, vessel.ErrTypeMismatchSentinel) {
        log.Println("Type mismatch during resolution")
    }
    
    // Errors include contextual information
    fmt.Printf("Error: %v\n", err)
}

// Check for specific error conditions
scope := c.BeginScope()
scope.End()

_, err = scope.Resolve("service")
if errors.Is(err, vessel.ErrScopeEnded) {
    // Handle ended scope
}

📊 Performance

Vessel is optimized for production use:

BenchmarkResolve_Singleton_Cached-16     100M    12.00 ns/op     0 B/op    0 allocs/op
BenchmarkResolve_Transient-16             94M    12.78 ns/op     0 B/op    0 allocs/op
BenchmarkScope_Create-16                  21M    56.46 ns/op   160 B/op    3 allocs/op
BenchmarkScope_Resolve_Cached-16          77M    15.60 ns/op     0 B/op    0 allocs/op
BenchmarkStart_10Services-16             351K     3.34 μs/op  6960 B/op   86 allocs/op
BenchmarkStart_100Services-16             45K    26.34 μs/op 58709 B/op  857 allocs/op
BenchmarkConcurrentResolve-16              8M      152 ns/op     0 B/op    0 allocs/op
BenchmarkConcurrentScope-16                7M      181 ns/op   448 B/op    4 allocs/op
Understanding the Benchmarks

Resolve_Singleton_Cached - Resolving an already-created singleton service. This is the most common operation in production. At ~12ns with zero allocations, it's essentially just a map lookup with a mutex read lock.

Resolve_Transient - Creating a new transient service instance each time. At ~13ns, the factory function is called but the service itself is simple (no dependencies), showing the framework's low overhead.

Scope_Create - Creating a new scope (e.g., for an HTTP request). At ~56ns with 160 bytes allocated, this is lightweight enough to create per-request without performance concerns.

Scope_Resolve_Cached - Resolving a scoped service that's already been created in the current scope. At ~16ns with zero allocations, subsequent resolutions within the same scope are very fast.

Start_10Services / Start_100Services - Starting services with lifecycle hooks. These scale linearly (~3.3μs for 10 services, ~26μs for 100 services), showing efficient startup even with many services. This happens once at application startup.

ConcurrentResolve - Multiple goroutines resolving the same singleton simultaneously. At ~152ns, the mutex contention is minimal, making Vessel safe for high-concurrency scenarios.

ConcurrentScope - Multiple goroutines creating and using separate scopes simultaneously. At ~181ns, isolated scopes have minimal contention, ideal for concurrent request handling.

Key Performance Characteristics
  • Cached singleton resolve: ~12ns (zero allocations) - The hot path for most applications
  • Transient service creation: ~13ns (zero allocations) - Minimal framework overhead
  • Scope creation: ~56ns (160 bytes, 3 allocations) - Efficient per-request scoping
  • Scoped service resolve: ~16ns cached (zero allocations) - Fast repeated access within scope
  • Thread-safe: Minimal contention under concurrent load (~10x slower than single-threaded)
  • Startup: Linear scaling, ~260ns per service with lifecycle management

What This Means for Your Application:

  • You can resolve services millions of times per second
  • Creating scopes per HTTP request adds negligible overhead (~56ns)
  • Concurrent access is safe and efficient for high-throughput services
  • Startup time is predictable and scales with service count

🛠️ Best Practices

  1. Register services at startup - Keep container immutable after initialization
  2. Use constructor injection - Prefer ProvideConstructor for cleaner, dig-like dependency resolution
  3. Use typed service keys - Prefer ServiceKey[T] over strings for type safety
  4. Use generics for type safety - Avoid any and type assertions
  5. Implement service lifecycle - Use Start/Stop for resource management
  6. Leverage scopes for requests - Create new scopes for HTTP handlers
  7. Use scope context storage - Store request-specific data with SetScoped/GetScoped
  8. Use In/Out structs for complex constructors - Embed vessel.In or vessel.Out for many dependencies
  9. Use lazy dependencies sparingly - Only for circular dependencies or expensive resources
  10. Declare dependencies explicitly - Use WithDependencies() for documentation
  11. Use middleware for cross-cutting concerns - Logging, metrics, security validation
  12. Query services for discovery - Use Query() and FindByGroup() for dynamic service discovery
  13. Batch register related services - Use RegisterServices() for cleaner code
  14. Test with mocks - Create fresh containers per test with mock services

🔄 Migration from Other DI Containers

From wire
// Before (wire)
//go:build wireinject
func InitializeApp() (*App, error) {
    wire.Build(
        NewDatabase,
        NewUserService,
        NewApp,
    )
    return nil, nil
}

// After (vessel)
func InitializeApp() (*App, error) {
    c := vessel.New()
    vessel.RegisterSingleton(c, "database", NewDatabase)
    vessel.RegisterSingleton(c, "userService", NewUserService)
    vessel.RegisterSingleton(c, "app", NewApp)
    
    return vessel.Resolve[*App](c, "app")
}
From dig
// Before (dig)
c := dig.New()
c.Provide(NewDatabase)
c.Provide(NewUserService)
c.Invoke(func(s *UserService) {
    // use service
})

// After (vessel) - dig-style constructor injection
c := vessel.New()
vessel.ProvideConstructor(c, NewDatabase)
vessel.ProvideConstructor(c, NewUserService)
userService, _ := vessel.InjectType[*UserService](c)

// dig In/Out structs are fully supported
type Params struct {
    dig.In  // Change to vessel.In
    DB *Database
}

// becomes
type Params struct {
    vessel.In
    DB *Database
}

// Optional, named, and group tags work the same way
type Deps struct {
    vessel.In
    Cache   *Cache `optional:"true"`
    Primary *DB    `name:"primary"`
}

// Value groups
c.Provide(NewHandler, dig.Group("handlers"))  // dig
vessel.ProvideConstructor(c, NewHandler, vessel.AsGroup("handlers"))  // vessel

// Interface registration
c.Provide(NewFileWriter, dig.As(new(io.Writer)))  // dig
vessel.ProvideConstructor(c, NewFileWriter, vessel.As(new(io.Writer)))  // vessel

📚 Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    
    "github.com/xraph/vessel"
)

// Domain types
type Config struct {
    DatabaseURL string
    Port        int
}

type Database struct {
    url string
}

func (d *Database) Name() string { return "database" }
func (d *Database) Start(ctx context.Context) error {
    fmt.Printf("Connecting to %s\n", d.url)
    return nil
}
func (d *Database) Stop(ctx context.Context) error {
    fmt.Println("Closing database connection")
    return nil
}

type UserRepository struct {
    db *Database
}

type UserService struct {
    repo *UserRepository
}

type HTTPServer struct {
    container vessel.Vessel
    port      int
}

func (s *HTTPServer) Name() string { return "http-server" }
func (s *HTTPServer) Start(ctx context.Context) error {
    fmt.Printf("Starting HTTP server on port %d\n", s.port)
    return nil
}
func (s *HTTPServer) Stop(ctx context.Context) error {
    fmt.Println("Shutting down HTTP server")
    return nil
}

func main() {
    // Initialize container
    c := vessel.New()
    
    // Register configuration
    vessel.RegisterValue(c, "config", &Config{
        DatabaseURL: "postgres://localhost/myapp",
        Port:        8080,
    })
    
    // Register services with typed dependencies
    vessel.RegisterSingletonWith[*Database](c, "database",
        vessel.Inject[*Config]("config"),
        func(cfg *Config) (*Database, error) {
            return &Database{url: cfg.DatabaseURL}, nil
        },
    )
    
    vessel.RegisterSingletonWith[*UserRepository](c, "userRepo",
        vessel.Inject[*Database]("database"),
        func(db *Database) (*UserRepository, error) {
            return &UserRepository{db: db}, nil
        },
    )
    
    vessel.RegisterSingletonWith[*UserService](c, "userService",
        vessel.Inject[*UserRepository]("userRepo"),
        func(repo *UserRepository) (*UserService, error) {
            return &UserService{repo: repo}, nil
        },
    )
    
    vessel.RegisterSingletonWith[*HTTPServer](c, "httpServer",
        vessel.Inject[*Config]("config"),
        func(cfg *Config) (*HTTPServer, error) {
            return &HTTPServer{
                container: c,
                port:      cfg.Port,
            }, nil
        },
    )
    
    // Start all services
    ctx := context.Background()
    if err := c.Start(ctx); err != nil {
        log.Fatalf("Failed to start services: %v", err)
    }
    defer c.Stop(ctx)
    
    // Application is running...
    fmt.Println("Application started successfully!")
    
    // Check health
    if err := c.Health(ctx); err != nil {
        log.Printf("Health check failed: %v", err)
    }
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

💬 Support


Built with ❤️ as part of the Forge ecosystem

Documentation

Index

Constants

View Source
const (
	// CodeInvalidFactory indicates a factory function is invalid or nil
	CodeInvalidFactory = "INVALID_FACTORY"

	// CodeServiceAlreadyExists indicates a service is already registered
	CodeServiceAlreadyExists = "SERVICE_ALREADY_EXISTS"

	// CodeServiceNotFound indicates a service was not found in the container
	CodeServiceNotFound = "SERVICE_NOT_FOUND"

	// CodeServiceError indicates an error occurred during service operation
	CodeServiceError = "SERVICE_ERROR"

	// CodeCircularDependency indicates a circular dependency was detected
	CodeCircularDependency = "CIRCULAR_DEPENDENCY"

	// CodeScopeEnded indicates operation on an ended scope
	CodeScopeEnded = "SCOPE_ENDED"

	// CodeTypeMismatch indicates a type mismatch during service resolution
	CodeTypeMismatch = "TYPE_MISMATCH"
)

Variables

View Source
var ErrCircularDependencySentinel = errs.NewError(CodeCircularDependency, "circular dependency", nil)

ErrCircularDependencySentinel is a sentinel error for circular dependency (for error checking).

View Source
var ErrInvalidFactory = errs.NewError(CodeInvalidFactory, "factory cannot be nil", nil)

ErrInvalidFactory is returned when a nil or invalid factory is provided.

View Source
var ErrScopeEnded = errs.NewError(CodeScopeEnded, "scope has ended", nil)

ErrScopeEnded is returned when operations are attempted on an ended scope.

View Source
var ErrServiceNotFoundSentinel = errs.NewError(CodeServiceNotFound, "service not found", nil)

ErrServiceNotFoundSentinel is a sentinel error for service not found (for error checking).

View Source
var ErrTypeMismatchSentinel = errs.NewError(CodeTypeMismatch, "type mismatch", nil)

ErrTypeMismatchSentinel is a sentinel error for type mismatch during resolution.

Functions

func ErrCircularDependency

func ErrCircularDependency(cycle []string) *errs.Error

ErrCircularDependency creates an error for circular dependency detection

func ErrServiceAlreadyExists

func ErrServiceAlreadyExists(serviceName string) *errs.Error

ErrServiceAlreadyExists creates an error for when a service is already registered

func ErrServiceNotFound

func ErrServiceNotFound(serviceName string) *errs.Error

ErrServiceNotFound creates an error for when a service is not found

func ErrTypeMismatch

func ErrTypeMismatch(serviceName string, actual any) *errs.Error

ErrTypeMismatch creates an error for type mismatch during resolution

func HasType

func HasType[T any](c Vessel) bool

HasType checks if a service of the given type is registered.

func HasTypeNamed

func HasTypeNamed[T any](c Vessel, name string) bool

HasTypeNamed checks if a named service of the given type is registered.

func Inject

func Inject[T any](c Vessel) (T, error)

Inject resolves a service by its type.

Example:

db, err := Inject[*Database](c)

func InjectGroup

func InjectGroup[T any](c Vessel, group string) ([]T, error)

InjectGroup resolves all services in a group as a slice.

Example:

handlers, err := InjectGroup[Handler](c, "http")

func InjectNamed

func InjectNamed[T any](c Vessel, name string) (T, error)

InjectNamed resolves a named service by its type. Use this when you have multiple implementations of the same type.

Example:

primaryDB, err := InjectNamed[*Database](c, "primary")
replicaDB, err := InjectNamed[*Database](c, "replica")

func InjectType deprecated

func InjectType[T any](c Vessel) (T, error)

Deprecated: Use Inject instead.

func MustInject

func MustInject[T any](c Vessel) T

MustInject resolves a service by its type, panicking on error.

func MustInjectGroup

func MustInjectGroup[T any](c Vessel, group string) []T

MustInjectGroup resolves all services in a group, panicking on error.

func MustInjectNamed

func MustInjectNamed[T any](c Vessel, name string) T

MustInjectNamed resolves a named service by its type, panicking on error.

func MustInjectType deprecated

func MustInjectType[T any](c Vessel) T

Deprecated: Use MustInject instead.

func NewServiceError

func NewServiceError(serviceName, operation string, cause error) *errs.Error

NewServiceError creates an error for service operations

func Provide

func Provide(c Vessel, constructor any, opts ...ConstructorOption) error

Provide registers a constructor function with automatic dependency resolution. Dependencies are inferred from function parameters and all return types (except error) are registered as services.

This follows the Uber dig pattern for constructor-based dependency injection:

  • Function parameters become dependencies (resolved by type)
  • Return types become provided services
  • Error return type is handled for construction failures

Example:

// Simple constructor
func NewUserService(db *Database, logger *Logger) *UserService {
    return &UserService{db: db, logger: logger}
}
Provide(c, NewUserService)

// Constructor with error
func NewDatabase(config *Config) (*Database, error) {
    return sql.Open(config.Driver, config.DSN)
}
Provide(c, NewDatabase)

// Using In struct for many dependencies
type ServiceParams struct {
    vessel.In
    DB     *Database
    Logger *Logger `optional:"true"`
}
func NewService(p ServiceParams) *Service {
    return &Service{db: p.DB, logger: p.Logger}
}
Provide(c, NewService)

func ProvideConstructor deprecated

func ProvideConstructor(c Vessel, constructor any, opts ...ConstructorOption) error

Deprecated: Use Provide instead.

func ProvideNamed

func ProvideNamed(c Vessel, name string, constructor any, opts ...ConstructorOption) error

ProvideNamed registers a named constructor function with automatic dependency resolution. This is equivalent to calling Provide with WithName(name).

Example:

ProvideNamed(c, "primary", NewPrimaryDB)
ProvideNamed(c, "replica", NewReplicaDB)

func ProvideValue

func ProvideValue[T any](c Vessel, value T, opts ...ConstructorOption) error

ProvideValue registers a pre-built instance as a singleton service. The instance is registered by its type and can be resolved with Inject[T].

Example:

cfg := &Config{Port: 8080}
ProvideValue(c, cfg)

// Later:
config, _ := Inject[*Config](c)

func QueryNames

func QueryNames(c Vessel, query ServiceQuery) []string

QueryNames returns the names of services matching the query criteria. This is more efficient than Query when you only need service names.

Example:

// Find all service names in the "db" group
names := vessel.QueryNames(c, vessel.ServiceQuery{
    Group: "db",
})

Types

type ConstructorOption

type ConstructorOption interface {
	// contains filtered or unexported methods
}

ConstructorOption configures how a constructor is registered

func As

func As(ifaces ...any) ConstructorOption

As registers the constructor result as additional interface types. This enables resolving the service by its interface types.

Example:

Provide(c, NewMyService, As(new(Reader), new(Writer)))

func AsGroup

func AsGroup(group string) ConstructorOption

AsGroup adds the constructor result to a value group. Services in the same group can be resolved together as a slice.

Example:

Provide(c, NewUserHandler, AsGroup("handlers"))
Provide(c, NewProductHandler, AsGroup("handlers"))
handlers := InjectGroup[Handler](c, "handlers") // Returns []Handler

func AsScoped

func AsScoped() ConstructorOption

AsScoped makes the constructor result scoped to request lifetime.

func AsSingleton

func AsSingleton() ConstructorOption

AsSingleton makes the constructor result a singleton (default).

func AsTransient

func AsTransient() ConstructorOption

AsTransient makes the constructor create a new instance on each resolve.

func WithAliases

func WithAliases(names ...string) ConstructorOption

WithAliases registers the constructor result under additional named variants. The primary registration is by TYPE (unnamed), and aliases provide named access. This allows the service to be resolved both by type and by name.

Example:

// Register by type with named aliases
Provide(c, NewDatabaseManager, WithAliases("manager", "db-manager"))

// Can resolve by type (unnamed):
mgr1, _ := Inject[*DatabaseManager](c)

// Or by any alias name:
mgr2, _ := InjectNamed[*DatabaseManager](c, "manager")
mgr3, _ := InjectNamed[*DatabaseManager](c, "db-manager")
// mgr1 == mgr2 == mgr3 (same singleton instance)

Note: If you use WithName(), that becomes the primary name and aliases are additional. Using only WithAliases() keeps the service unnamed (type-based) as primary.

func WithEager

func WithEager() ConstructorOption

WithEager causes the constructor to be invoked immediately after registration, rather than waiting for first use (lazy). This is useful for:

  • Fail-fast behavior: catch construction errors at startup
  • Services that need to initialize during app startup (servers, connections)
  • Pre-warming caches

Example:

// Start database connection immediately, fail if can't connect
Provide(c, NewDatabase, WithEager())

// Lazy (default): Wait until first use
Provide(c, NewCache)

func WithName

func WithName(name string) ConstructorOption

WithName gives the constructor result a name for disambiguation. Use this when you have multiple implementations of the same type.

Example:

Provide(c, NewPrimaryDB, WithName("primary"))
Provide(c, NewReplicaDB, WithName("replica"))

type DependencyGraph

type DependencyGraph struct {
	// contains filtered or unexported fields
}

DependencyGraph manages service dependencies.

func NewDependencyGraph

func NewDependencyGraph() *DependencyGraph

NewDependencyGraph creates a new dependency graph.

func (*DependencyGraph) AddNode

func (g *DependencyGraph) AddNode(name string, dependencies []string)

AddNode adds a node with its dependencies (string-based, backward compatible). Nodes are processed in the order they are added (FIFO) when no dependencies exist.

func (*DependencyGraph) AddNodeWithDeps

func (g *DependencyGraph) AddNodeWithDeps(name string, deps []di.Dep)

AddNodeWithDeps adds a node with full Dep specs. This is the new API that supports lazy/optional dependencies.

func (*DependencyGraph) GetDependencies

func (g *DependencyGraph) GetDependencies(name string) []string

GetDependencies returns the dependency names for a node.

func (*DependencyGraph) GetDeps

func (g *DependencyGraph) GetDeps(name string) []di.Dep

GetDeps returns the full Dep specs for a node.

func (*DependencyGraph) GetEagerDependencies

func (g *DependencyGraph) GetEagerDependencies(name string) []string

GetEagerDependencies returns only the eager (non-lazy) dependencies. These are the ones that must be resolved before the service can be created.

func (*DependencyGraph) HasNode

func (g *DependencyGraph) HasNode(name string) bool

HasNode checks if a node exists in the graph.

func (*DependencyGraph) TopologicalSort

func (g *DependencyGraph) TopologicalSort() ([]string, error)

TopologicalSort returns nodes in dependency order. Nodes without dependencies maintain their registration order (FIFO). Returns error if circular dependency detected.

func (*DependencyGraph) TopologicalSortEagerOnly

func (g *DependencyGraph) TopologicalSortEagerOnly() ([]string, error)

TopologicalSortEagerOnly returns nodes sorted considering only eager dependencies. Lazy dependencies are excluded from the ordering since they're resolved on-demand.

type Factory

type Factory = di.Factory

Factory creates a service instance.

type FuncMiddleware

type FuncMiddleware struct {
	BeforeResolveFunc func(ctx context.Context, name string) error
	AfterResolveFunc  func(ctx context.Context, name string, service any, err error) error
	BeforeStartFunc   func(ctx context.Context, name string) error
	AfterStartFunc    func(ctx context.Context, name string, err error) error
}

FuncMiddleware wraps functions as Middleware.

func (*FuncMiddleware) AfterResolve

func (f *FuncMiddleware) AfterResolve(ctx context.Context, name string, service any, err error) error

AfterResolve implements Middleware.

func (*FuncMiddleware) AfterStart

func (f *FuncMiddleware) AfterStart(ctx context.Context, name string, err error) error

AfterStart implements Middleware.

func (*FuncMiddleware) BeforeResolve

func (f *FuncMiddleware) BeforeResolve(ctx context.Context, name string) error

BeforeResolve implements Middleware.

func (*FuncMiddleware) BeforeStart

func (f *FuncMiddleware) BeforeStart(ctx context.Context, name string) error

BeforeStart implements Middleware.

type In

type In struct{}

In is a marker type that should be embedded in structs to indicate they are parameter objects. Fields of the struct will be treated as dependencies to inject. This follows the dig pattern for constructor injection.

Example:

type ServiceParams struct {
    vessel.In

    DB     *Database
    Logger *Logger           `optional:"true"`
    Cache  *Cache            `name:"redis"`
    Handlers []http.Handler  `group:"http"`
}

type Lazy

type Lazy[T any] struct {
	// contains filtered or unexported fields
}

Lazy wraps a dependency that is resolved on first access. This is useful for breaking circular dependencies or deferring resolution of expensive services until they're actually needed.

func NewLazy

func NewLazy[T any](container di.Container, name string) *Lazy[T]

NewLazy creates a new lazy dependency wrapper.

func (*Lazy[T]) Get

func (l *Lazy[T]) Get() (T, error)

Get resolves the dependency and returns it. The resolution happens only once; subsequent calls return the cached value.

func (*Lazy[T]) IsResolved

func (l *Lazy[T]) IsResolved() bool

IsResolved returns true if the dependency has been resolved.

func (*Lazy[T]) MustGet

func (l *Lazy[T]) MustGet() T

MustGet resolves the dependency and returns it, panicking on error.

func (*Lazy[T]) Name

func (l *Lazy[T]) Name() string

Name returns the name of the dependency.

type Middleware

type Middleware interface {
	// BeforeResolve is called before resolving a service.
	// Return error to abort resolution.
	BeforeResolve(ctx context.Context, name string) error

	// AfterResolve is called after resolving a service.
	// Called even if resolution failed (service and err may both be set).
	AfterResolve(ctx context.Context, name string, service any, err error) error

	// BeforeStart is called before starting a service.
	// Return error to abort start.
	BeforeStart(ctx context.Context, name string) error

	// AfterStart is called after starting a service.
	// Called even if start failed.
	AfterStart(ctx context.Context, name string, err error) error
}

Middleware provides hooks for intercepting container operations. Middleware can be used for logging, metrics, security, testing, etc.

type OptionalLazy

type OptionalLazy[T any] struct {
	// contains filtered or unexported fields
}

OptionalLazy wraps an optional dependency that is resolved on first access. Returns nil without error if the dependency is not found.

func NewOptionalLazy

func NewOptionalLazy[T any](container di.Container, name string) *OptionalLazy[T]

NewOptionalLazy creates a new optional lazy dependency wrapper.

func (*OptionalLazy[T]) Get

func (l *OptionalLazy[T]) Get() (T, error)

Get resolves the dependency and returns it. Returns the zero value without error if the dependency is not found.

func (*OptionalLazy[T]) IsFound

func (l *OptionalLazy[T]) IsFound() bool

IsFound returns true if the dependency was found (only valid after resolution).

func (*OptionalLazy[T]) IsResolved

func (l *OptionalLazy[T]) IsResolved() bool

IsResolved returns true if the dependency has been resolved.

func (*OptionalLazy[T]) MustGet

func (l *OptionalLazy[T]) MustGet() T

MustGet resolves the dependency and returns it, panicking on error. Returns the zero value if the dependency is not found (does not panic).

func (*OptionalLazy[T]) Name

func (l *OptionalLazy[T]) Name() string

Name returns the name of the dependency.

type Out

type Out struct{}

Out is a marker type that should be embedded in structs to indicate they are result objects. Each field of the struct will be registered as a separate service. This follows the dig pattern for multi-value returns.

Example:

type ServiceResult struct {
    vessel.Out

    UserService    *UserService
    ProductService *ProductService `name:"products"`
    Handler        http.Handler    `group:"http"`
}

type Provider

type Provider[T any] struct {
	// contains filtered or unexported fields
}

Provider wraps a dependency that creates new instances on each access. This is useful for transient dependencies where a fresh instance is needed each time.

func NewProvider

func NewProvider[T any](container di.Container, name string) *Provider[T]

NewProvider creates a new provider for transient dependencies.

func (*Provider[T]) MustProvide

func (p *Provider[T]) MustProvide() T

MustProvide resolves and returns a new instance, panicking on error.

func (*Provider[T]) Name

func (p *Provider[T]) Name() string

Name returns the name of the dependency.

func (*Provider[T]) Provide

func (p *Provider[T]) Provide() (T, error)

Provide resolves and returns a new instance of the dependency. Each call may return a different instance (if the service is transient).

type RegisterOption

type RegisterOption = di.RegisterOption

RegisterOption is a configuration option for service registration.

type Scope

type Scope = di.Scope

Scope represents a lifetime scope for scoped services Typically used for HTTP requests or other bounded operations.

type ServiceInfo

type ServiceInfo = di.ServiceInfo

ServiceInfo contains diagnostic information.

func FindByGroup

func FindByGroup(c Vessel, group string) []ServiceInfo

FindByGroup returns all services in a specific group.

func FindByLifecycle

func FindByLifecycle(c Vessel, lifecycle string) []ServiceInfo

FindByLifecycle returns all services with a specific lifecycle.

func FindNotStarted

func FindNotStarted(c Vessel) []ServiceInfo

FindNotStarted returns all services that have not been started.

func FindStarted

func FindStarted(c Vessel) []ServiceInfo

FindStarted returns all services that have been started.

func Query

func Query(c Vessel, query ServiceQuery) []ServiceInfo

Query returns detailed information about services matching the query criteria. Returns a slice of ServiceInfo for all matching services.

Example:

// Find all singleton services in the "api" group
started := true
results := vessel.Query(c, vessel.ServiceQuery{
    Lifecycle: "singleton",
    Group: "api",
    Started: &started,
})

type ServiceQuery

type ServiceQuery struct {
	// Lifecycle filters by service lifecycle (singleton, transient, scoped).
	// Empty string matches all lifecycles.
	Lifecycle string

	// Group filters by service group.
	// Empty string matches all groups.
	Group string

	// Metadata filters by service metadata key-value pairs.
	// All specified metadata must match for a service to be included.
	Metadata map[string]string

	// Started filters by whether the service has been started.
	// nil matches all services (started and not started).
	Started *bool
}

ServiceQuery defines criteria for querying services.

type Vessel

type Vessel = di.Container

Vessel provides dependency injection with lifecycle management.

func New

func New() Vessel

New creates a new DI container.

Jump to

Keyboard shortcuts

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