Vessel provides wrapper types that let you defer dependency resolution, handle missing services gracefully, and create fresh instances on demand.
Lazy Dependencies01
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
| Method | Description |
Get() (T, error) | Resolve on first call, return cached value after |
MustGet() T | Like Get but panics on error |
IsResolved() bool | Whether the dependency has been resolved |
Name() string | The service name being resolved |
Optional Lazy Dependencies02
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
| Method | Description |
Get() (T, error) | Resolve on first call; zero value if not found |
MustGet() T | Like Get but panics on error (not on missing) |
IsResolved() bool | Whether resolution has been attempted |
IsFound() bool | Whether the dependency was found (valid after resolution) |
Name() string | The service name being resolved |
Provider (Transient Access)03
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
| Method | Description |
Provide() (T, error) | Resolve and return a (potentially new) instance |
MustProvide() T | Like Provide but panics on error |
Name() string | The service name being resolved |
Discovery Checks04
Check whether a service is registered before resolving.
// Check by type (constructor-registered services)
if vessel.HasType[*CacheService](c) {
cache, _ := vessel.Inject[*CacheService](c)
_ = cache
}
// Check named type
if vessel.HasTypeNamed[*Database](c, "replica") {
replica, _ := vessel.InjectNamed[*Database](c, "replica")
_ = replica
}Must Variants05
For startup code where failure should be fatal, use the Must variants that panic instead of returning errors:
// Type-based must
userSvc := vessel.MustInject[*UserService](c)
// Named must
primary := vessel.MustInjectNamed[*Database](c, "primary")
// Group must
handlers := vessel.MustInjectGroup[Handler](c, "http")Use Must variants only during application startup or in tests. Prefer error-returning variants in production request paths.