Lazy and Optional

Defer resolution and handle optional dependencies safely

Vessel provides wrapper types that let you defer dependency resolution, handle missing services gracefully, and create fresh instances on demand.

Lazy Dependencies

vessel.Lazy[T] wraps a dependency that is resolved on first access. The resolution happens once; subsequent calls return the cached result. This is useful for breaking circular dependencies or deferring expensive initialization.

import "github.com/xraph/vessel"

type UserService struct {
    cache *vessel.Lazy[*CacheService]
}

func NewUserService(c vessel.Vessel) *UserService {
    return &UserService{
        cache: vessel.NewLazy[*CacheService](c, "cache"),
    }
}

func (s *UserService) GetUser(id string) (*User, error) {
    cache, err := s.cache.Get()
    if err != nil {
        return nil, err
    }
    return cache.Get("user:" + id)
}

Lazy API

MethodDescription
Get() (T, error)Resolve on first call, return cached value after
MustGet() TLike Get but panics on error
IsResolved() boolWhether the dependency has been resolved
Name() stringThe service name being resolved

Optional Lazy Dependencies

vessel.OptionalLazy[T] resolves on first access but returns the zero value (without error) if the service is not registered. Use this when a dependency is nice to have but not required.

type MetricsService struct {
    tracer *vessel.OptionalLazy[*Tracer]
}

func NewMetricsService(c vessel.Vessel) *MetricsService {
    return &MetricsService{
        tracer: vessel.NewOptionalLazy[*Tracer](c, "tracer"),
    }
}

func (s *MetricsService) Record(name string) {
    tracer, err := s.tracer.Get()
    if err != nil {
        // Handle unexpected resolution error
        return
    }
    if tracer != nil {
        tracer.Span(name)
    }
}

OptionalLazy API

MethodDescription
Get() (T, error)Resolve on first call; zero value if not found
MustGet() TLike Get but panics on error (not on missing)
IsResolved() boolWhether resolution has been attempted
IsFound() boolWhether the dependency was found (valid after resolution)
Name() stringThe service name being resolved

Provider (Transient Access)

vessel.Provider[T] wraps a dependency and resolves a fresh instance on every .Provide() call. Use this when you need a new instance each time (e.g., per-request objects).

type Handler struct {
    reqProvider *vessel.Provider[*Request]
}

func NewHandler(c vessel.Vessel) *Handler {
    return &Handler{
        reqProvider: vessel.NewProvider[*Request](c, "request"),
    }
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    req, err := h.reqProvider.Provide()
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    // req is a fresh instance each call
    _ = req
}

Provider API

MethodDescription
Provide() (T, error)Resolve and return a (potentially new) instance
MustProvide() TLike Provide but panics on error
Name() stringThe service name being resolved

Discovery Checks

Check whether a service is registered before resolving.

// Check by type (constructor-registered services)
if vessel.HasType[*CacheService](c) {
    cache, _ := vessel.InjectType[*CacheService](c)
    _ = cache
}

// Check named type
if vessel.HasTypeNamed[*Database](c, "replica") {
    replica, _ := vessel.InjectNamed[*Database](c, "replica")
    _ = replica
}

// Check by name (name-based registrations)
if c.Has("metrics") {
    metrics, _ := vessel.Resolve[*MetricsClient](c, "metrics")
    _ = metrics
}

Must Variants

For startup code where failure should be fatal, use the Must variants that panic instead of returning errors:

// Panic if resolution fails
db := vessel.Must[*Database](c, "database")

// Type-based must
userSvc := vessel.MustInjectType[*UserService](c)

// Named must
primary := vessel.MustInjectNamed[*Database](c, "primary")

// Group must
handlers := vessel.MustInjectGroup[Handler](c, "http")

// Scope must
reqCtx := vessel.MustScope[*RequestContext](scope, "requestCtx")

Use Must variants only during application startup or in tests. Prefer error-returning variants in production request paths.

How is this guide?

On this page