Service Groups

Collect multiple services under a group name for batch resolution.

The Problem

You need to work with multiple services of the same type - validators, middleware, handlers:

// Manually collecting
var validators []Validator
validators = append(validators, emailValidator)
validators = append(validators, phoneValidator)
validators = append(validators, addressValidator)

The Solution: Groups

// Register with group tag
services.AddSingleton(NewEmailValidator, godi.Group("validators"))
services.AddSingleton(NewPhoneValidator, godi.Group("validators"))
services.AddSingleton(NewAddressValidator, godi.Group("validators"))

// Resolve all at once
validators := godi.MustResolveGroup[Validator](provider, "validators")
// validators is []Validator with all three

Registration

Use godi.Group() to add services to a group:

// Multiple validators
services.AddSingleton(NewEmailValidator, godi.Group("validators"))
services.AddSingleton(NewPhoneValidator, godi.Group("validators"))
services.AddSingleton(NewAddressValidator, godi.Group("validators"))

// Multiple middleware
services.AddSingleton(NewLoggingMiddleware, godi.Group("middleware"))
services.AddSingleton(NewAuthMiddleware, godi.Group("middleware"))
services.AddSingleton(NewRateLimitMiddleware, godi.Group("middleware"))

Resolution

// Get all services in group
validators := godi.MustResolveGroup[Validator](provider, "validators")

for _, v := range validators {
    if err := v.Validate(input); err != nil {
        return err
    }
}

Use Cases

Validation Chain

type Validator interface {
    Validate(data any) error
}

// Register validators
services.AddSingleton(NewRequiredFieldValidator, godi.Group("validators"))
services.AddSingleton(NewEmailFormatValidator, godi.Group("validators"))
services.AddSingleton(NewPhoneFormatValidator, godi.Group("validators"))
services.AddSingleton(NewAddressValidator, godi.Group("validators"))

// Use in service
type UserService struct {
    validators []Validator
}

func NewUserService(validators []Validator) *UserService {
    return &UserService{validators: validators}
}

func (s *UserService) Create(user *User) error {
    // Run all validators
    for _, v := range s.validators {
        if err := v.Validate(user); err != nil {
            return err
        }
    }
    // Create user...
    return nil
}

Middleware Stack

type Middleware func(http.Handler) http.Handler

services.AddSingleton(NewLoggingMiddleware, godi.Group("middleware"))
services.AddSingleton(NewRecoveryMiddleware, godi.Group("middleware"))
services.AddSingleton(NewCORSMiddleware, godi.Group("middleware"))
services.AddSingleton(NewAuthMiddleware, godi.Group("middleware"))

// Apply all middleware
func BuildHandler(provider godi.Provider, handler http.Handler) http.Handler {
    middlewares := godi.MustResolveGroup[Middleware](provider, "middleware")

    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }

    return handler
}

Event Handlers

type EventHandler interface {
    Handle(event Event) error
}

services.AddSingleton(NewLoggingHandler, godi.Group("handlers"))
services.AddSingleton(NewMetricsHandler, godi.Group("handlers"))
services.AddSingleton(NewNotificationHandler, godi.Group("handlers"))
services.AddSingleton(NewAuditHandler, godi.Group("handlers"))

// Broadcast events
type EventBus struct {
    handlers []EventHandler
}

func (b *EventBus) Publish(event Event) {
    for _, h := range b.handlers {
        go h.Handle(event)
    }
}

Plugin System

type Plugin interface {
    Name() string
    Init() error
    Shutdown() error
}

services.AddSingleton(NewAuthPlugin, godi.Group("plugins"))
services.AddSingleton(NewCachePlugin, godi.Group("plugins"))
services.AddSingleton(NewMetricsPlugin, godi.Group("plugins"))

func InitializeApp(provider godi.Provider) error {
    plugins := godi.MustResolveGroup[Plugin](provider, "plugins")

    for _, p := range plugins {
        log.Printf("Initializing plugin: %s", p.Name())
        if err := p.Init(); err != nil {
            return err
        }
    }

    return nil
}

With Parameter Objects

Use the group tag to inject groups:

type ApplicationParams struct {
    godi.In

    Logger      Logger
    Config      *Config
    Validators  []Validator    `group:"validators"`
    Middlewares []Middleware   `group:"middleware"`
    Handlers    []EventHandler `group:"handlers"`
}

func NewApplication(params ApplicationParams) *Application {
    return &Application{
        logger:      params.Logger,
        config:      params.Config,
        validators:  params.Validators,
        middlewares: params.Middlewares,
        handlers:    params.Handlers,
    }
}

Optional Groups

type ServiceParams struct {
    godi.In

    // Required dependencies
    Logger Logger

    // Optional group - empty slice if no services registered
    Plugins []Plugin `group:"plugins" optional:"true"`
}

func NewService(params ServiceParams) *Service {
    svc := &Service{logger: params.Logger}

    // Check if plugins available
    if len(params.Plugins) > 0 {
        for _, p := range params.Plugins {
            svc.RegisterPlugin(p)
        }
    }

    return svc
}

Combining Keys and Groups

A service can have both a key and belong to groups:

// Service with name AND in group
services.AddSingleton(NewEmailValidator,
    godi.Name("email"),
    godi.Group("validators"),
)

// Resolve by name
emailValidator := godi.MustResolveKeyed[Validator](provider, "email")

// Or get all validators
allValidators := godi.MustResolveGroup[Validator](provider, "validators")

Ordering

Group members are resolved in registration order:

services.AddSingleton(NewFirst, godi.Group("ordered"))   // Index 0
services.AddSingleton(NewSecond, godi.Group("ordered"))  // Index 1
services.AddSingleton(NewThird, godi.Group("ordered"))   // Index 2

items := godi.MustResolveGroup[Item](provider, "ordered")
// items[0] = First, items[1] = Second, items[2] = Third

Empty Groups

If no services are registered to a group, resolution returns an empty slice:

// No services registered to "missing" group
items := godi.MustResolveGroup[Item](provider, "missing")
// items is []Item{} (empty, not nil)

See also: Keyed Services | Parameter Objects