Interface Binding

Register concrete types to satisfy interfaces.

The Problem

You have a concrete type but want to resolve by interface:

type consoleLogger struct{}
func (c *consoleLogger) Log(msg string) { fmt.Println(msg) }

type Logger interface {
    Log(string)
}

// Register concrete
services.AddSingleton(NewConsoleLogger)  // Returns *consoleLogger

// Want to resolve by interface
logger := godi.MustResolve[Logger](provider)  // Error: Logger not registered

The Solution: As Option

Use godi.As[T]() to register a concrete type as an interface:

services.AddSingleton(NewConsoleLogger, godi.As[Logger]())

// Now resolvable by interface
logger := godi.MustResolve[Logger](provider)

Basic Usage

// Interface
type Cache interface {
    Get(key string) (any, bool)
    Set(key string, value any)
}

// Concrete implementation
type redisCache struct {
    client *redis.Client
}

func NewRedisCache(config *Config) *redisCache {
    return &redisCache{
        client: redis.NewClient(&redis.Options{Addr: config.RedisAddr}),
    }
}

// Register as interface
services.AddSingleton(NewRedisCache, godi.As[Cache]())

// Resolve by interface
cache := godi.MustResolve[Cache](provider)

Multiple Interfaces

A type can implement and be registered as multiple interfaces:

type userStore struct {
    db *sql.DB
}

// Implements multiple interfaces
type UserReader interface { GetUser(id int) *User }
type UserWriter interface { SaveUser(user *User) error }
type UserRepository interface {
    GetUser(id int) *User
    SaveUser(user *User) error
}

services.AddSingleton(NewUserStore,
    godi.As[UserReader](),
    godi.As[UserWriter](),
    godi.As[UserRepository](),
)

// Resolve by any interface
reader := godi.MustResolve[UserReader](provider)
writer := godi.MustResolve[UserWriter](provider)
repo := godi.MustResolve[UserRepository](provider)
// All return the same *userStore instance (singleton)

With Keys and Groups

Combine with other options:

// Named interface
services.AddSingleton(NewFileLogger,
    godi.Name("file"),
    godi.As[Logger](),
)

// Resolve by key and interface
fileLogger := godi.MustResolveKeyed[Logger](provider, "file")

// Interface in group
services.AddSingleton(NewEmailValidator,
    godi.Group("validators"),
    godi.As[Validator](),
)

validators := godi.MustResolveGroup[Validator](provider, "validators")

Use Cases

Swappable Implementations

// Production
services.AddSingleton(NewProductionEmailer, godi.As[Emailer]())

// Testing
services.AddSingleton(NewMockEmailer, godi.As[Emailer]())

// Code uses interface
type NotificationService struct {
    emailer Emailer  // Interface
}

Repository Pattern

type UserRepository interface {
    FindByID(id int) (*User, error)
    FindByEmail(email string) (*User, error)
    Save(user *User) error
}

// PostgreSQL implementation
type postgresUserRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) *postgresUserRepository {
    return &postgresUserRepository{db: db}
}

// Register implementation as interface
services.AddScoped(NewUserRepository, godi.As[UserRepository]())

// Service depends on interface
type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

Dependency Inversion

// Domain layer defines interface
type OrderPlacer interface {
    PlaceOrder(order *Order) error
}

// Infrastructure implements it
type stripeOrderPlacer struct {
    client *stripe.Client
}

func NewStripeOrderPlacer(config *Config) *stripeOrderPlacer {
    return &stripeOrderPlacer{
        client: stripe.NewClient(config.StripeKey),
    }
}

// Register infrastructure as domain interface
services.AddSingleton(NewStripeOrderPlacer, godi.As[OrderPlacer]())

// Domain service uses interface
type CheckoutService struct {
    placer OrderPlacer
}

With Parameter Objects

Reference interfaces in parameter objects:

type ServiceParams struct {
    godi.In

    Logger Logger     // Interface
    Cache  Cache      // Interface
    Repo   Repository // Interface
}

func NewService(params ServiceParams) *Service {
    return &Service{
        logger: params.Logger,
        cache:  params.Cache,
        repo:   params.Repo,
    }
}

Testing

Easy to swap implementations for testing:

// Production setup
func ProductionModule() godi.Module {
    return func(services *godi.ServiceCollection) {
        services.AddSingleton(NewProductionDB, godi.As[Database]())
        services.AddSingleton(NewProductionCache, godi.As[Cache]())
    }
}

// Test setup
func TestModule() godi.Module {
    return func(services *godi.ServiceCollection) {
        services.AddSingleton(NewMockDB, godi.As[Database]())
        services.AddSingleton(NewMockCache, godi.As[Cache]())
    }
}

// In tests
services := godi.NewCollection()
services.AddModule(TestModule())  // Use mocks

Common Mistakes

Resolving Concrete When Registered as Interface

services.AddSingleton(NewConsoleLogger, godi.As[Logger]())

// Error: *consoleLogger not registered directly
logger := godi.MustResolve[*consoleLogger](provider)

// Correct: resolve by interface
logger := godi.MustResolve[Logger](provider)

Forgetting As Option

// Only registers *consoleLogger
services.AddSingleton(NewConsoleLogger)

// Error: Logger interface not registered
logger := godi.MustResolve[Logger](provider)

See also: Keyed Services | Parameter Objects