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 *Cache

Must 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?

On this page