Keyed Services

Register and resolve multiple implementations of the same type using keys.

The Problem

You need multiple database connections, cache implementations, or payment gateways - all of the same type:

// Can't do this - same type registered twice
services.AddSingleton(NewPrimaryDB)   // Database
services.AddSingleton(NewReplicaDB)   // Also Database - conflict!

The Solution: Keys

services.AddSingleton(NewPrimaryDB, godi.Name("primary"))
services.AddSingleton(NewReplicaDB, godi.Name("replica"))

// Resolve by key
primary := godi.MustResolveKeyed[Database](provider, "primary")
replica := godi.MustResolveKeyed[Database](provider, "replica")

Registration

Use godi.Name() to assign a key:

// Database connections
services.AddSingleton(NewPrimaryDB, godi.Name("primary"))
services.AddSingleton(NewReplicaDB, godi.Name("replica"))
services.AddSingleton(NewAnalyticsDB, godi.Name("analytics"))

// Cache implementations
services.AddSingleton(NewRedisCache, godi.Name("redis"))
services.AddSingleton(NewMemoryCache, godi.Name("memory"))

Resolution

// By key
primary := godi.MustResolveKeyed[Database](provider, "primary")
replica := godi.MustResolveKeyed[Database](provider, "replica")

// With error handling
analytics, err := godi.ResolveKeyed[Database](provider, "analytics")
if err != nil {
    // Key not found or resolution error
}

Use Cases

Multiple Database Connections

type DatabaseManager struct {
    primary   Database
    replica   Database
    analytics Database
}

func NewDatabaseManager(provider godi.Provider) *DatabaseManager {
    return &DatabaseManager{
        primary:   godi.MustResolveKeyed[Database](provider, "primary"),
        replica:   godi.MustResolveKeyed[Database](provider, "replica"),
        analytics: godi.MustResolveKeyed[Database](provider, "analytics"),
    }
}

func (m *DatabaseManager) Read(query string) Result {
    return m.replica.Query(query)  // Use replica for reads
}

func (m *DatabaseManager) Write(query string) Result {
    return m.primary.Exec(query)  // Use primary for writes
}

Strategy Pattern

type PaymentStrategy interface {
    Process(amount float64) error
}

// Register strategies
services.AddSingleton(NewStripeStrategy, godi.Name("stripe"), godi.As[PaymentStrategy]())
services.AddSingleton(NewPayPalStrategy, godi.Name("paypal"), godi.As[PaymentStrategy]())
services.AddSingleton(NewSquareStrategy, godi.Name("square"), godi.As[PaymentStrategy]())

// Select at runtime
func (s *PaymentService) Process(method string, amount float64) error {
    strategy := godi.MustResolveKeyed[PaymentStrategy](s.provider, method)
    return strategy.Process(amount)
}

Environment-Specific Services

func RegisterEmailService(services *godi.ServiceCollection, env string) {
    switch env {
    case "development":
        services.AddSingleton(NewMockEmailer, godi.Name("email"))
    case "staging":
        services.AddSingleton(NewSandboxEmailer, godi.Name("email"))
    case "production":
        services.AddSingleton(NewSESEmailer, godi.Name("email"))
    }
}

// Same resolution everywhere
emailer := godi.MustResolveKeyed[Emailer](provider, "email")

With Parameter Objects

Use the name tag to inject keyed services:

type ServiceParams struct {
    godi.In

    PrimaryDB   Database `name:"primary"`
    ReplicaDB   Database `name:"replica"`
    RedisCache  Cache    `name:"redis"`
    MemoryCache Cache    `name:"memory"`
}

func NewService(params ServiceParams) *Service {
    return &Service{
        primary: params.PrimaryDB,
        replica: params.ReplicaDB,
        redis:   params.RedisCache,
        memory:  params.MemoryCache,
    }
}

Best Practices

Use Constants for Keys

const (
    PrimaryDB   = "primary"
    ReplicaDB   = "replica"
    RedisCache  = "redis"
    MemoryCache = "memory"
)

// Registration
services.AddSingleton(NewPrimaryDB, godi.Name(PrimaryDB))

// Resolution
db := godi.MustResolveKeyed[Database](provider, PrimaryDB)

Fallback Pattern

func GetCache(provider godi.Provider) Cache {
    // Try primary
    cache, err := godi.ResolveKeyed[Cache](provider, "redis")
    if err == nil {
        return cache
    }

    // Fallback
    cache, err = godi.ResolveKeyed[Cache](provider, "memory")
    if err == nil {
        return cache
    }

    // Default
    return NewDefaultCache()
}

Common Mistakes

Duplicate Keys

// Error: same key twice
services.AddSingleton(NewServiceA, godi.Name("service"))
services.AddSingleton(NewServiceB, godi.Name("service"))  // Conflict!

// Correct: unique keys
services.AddSingleton(NewServiceA, godi.Name("serviceA"))
services.AddSingleton(NewServiceB, godi.Name("serviceB"))

Wrong Type

services.AddSingleton(NewLogger, godi.Name("logger"))

// Error: wrong type
cache := godi.MustResolveKeyed[Cache](provider, "logger")  // Panic!

// Correct: matching type
logger := godi.MustResolveKeyed[Logger](provider, "logger")

See also: Service Groups | Parameter Objects