Typed Keys
Compile-time safe service keys for registration and resolution
ServiceKey[T] provides compile-time type safety for name-based service registration and resolution. Instead of passing separate string names and type parameters, a single key encodes both the name and the expected type.
Defining Keys
Define keys as package-level variables. The type parameter T is the expected service type, and the string argument is the underlying name.
import "github.com/xraph/vessel"
var (
DatabaseKey = vessel.NewServiceKey[*Database]("database")
UserServiceKey = vessel.NewServiceKey[*UserService]("userService")
CacheKey = vessel.NewServiceKey[*Cache]("cache")
)Registration
Use RegisterWithKey to register a service with a typed key. Lifecycle options work the same as with Register.
vessel.RegisterWithKey(c, DatabaseKey, func(c vessel.Vessel) (*Database, error) {
return &Database{DSN: "postgres://localhost:5432/app"}, nil
}, vessel.Singleton())
vessel.RegisterWithKey(c, CacheKey, func(c vessel.Vessel) (*Cache, error) {
db := vessel.MustWithKey(c, DatabaseKey)
return &Cache{db: db}, nil
}, vessel.Singleton())Resolution
Use ResolveWithKey to resolve. The return type is inferred from the key -- no explicit type parameter needed.
db, err := vessel.ResolveWithKey(c, DatabaseKey)
// db is *Database -- type is enforced by the key
cache, err := vessel.ResolveWithKey(c, CacheKey)
// cache is *CacheMust Variant
For startup code where failure should be fatal:
db := vessel.MustWithKey(c, DatabaseKey)Inspection
Check registration status and inspect service metadata using typed keys:
// Check if registered
if vessel.HasKey(c, DatabaseKey) {
fmt.Println("Database is registered")
}
// Check if started
if vessel.IsStartedKey(c, DatabaseKey) {
fmt.Println("Database service has started")
}
// Inspect service metadata
info := vessel.InspectKey(c, DatabaseKey)
fmt.Printf("Name: %s, Lifecycle: %s, Started: %v\n", info.Name, info.Lifecycle, info.Started)Batch Registration with Keys
Register multiple keyed services in a single call:
var (
DatabaseKey = vessel.NewServiceKey[*Database]("database")
CacheKey = vessel.NewServiceKey[*Cache]("cache")
LoggerKey = vessel.NewServiceKey[*Logger]("logger")
)
// Note: all services in a batch must share the same type parameter
err := vessel.RegisterKeyedServices(c,
vessel.KeyedService(DatabaseKey, NewDatabase, vessel.Singleton()),
)For services of different types, register them individually with RegisterWithKey.
When to Use Typed Keys
Use typed keys when:
- You need name-based registration but want compile-time type safety
- Multiple parts of your codebase register and resolve the same service
- You want IDE autocomplete for service names
- You are building a library that exposes service keys for consumers
Prefer ProvideConstructor + InjectType when:
- Your dependency graph is fully type-driven with no name ambiguity
- You don't need to share service identifiers across packages
How is this guide?