Service Groups
Collect multiple services under a group name for batch operations and plugin systems.
Understanding Service Groups
Service groups allow you to register multiple services under the same group name and resolve them all at once:
// Register middleware in a group
services.AddScoped(NewAuthMiddleware, godi.Group("middleware"))
services.AddScoped(NewLoggingMiddleware, godi.Group("middleware"))
services.AddScoped(NewCORSMiddleware, godi.Group("middleware"))
// Resolve all middleware at once
middlewares := godi.MustResolveGroup[Middleware](provider, "middleware")
for _, mw := range middlewares {
app.Use(mw)
}
Registration with Groups
Basic Group Registration
// Register multiple handlers
services.AddSingleton(NewUserHandler, godi.Group("handlers"))
services.AddSingleton(NewOrderHandler, godi.Group("handlers"))
services.AddSingleton(NewProductHandler, godi.Group("handlers"))
// Register multiple validators
services.AddTransient(NewEmailValidator, godi.Group("validators"))
services.AddTransient(NewPhoneValidator, godi.Group("validators"))
services.AddTransient(NewAddressValidator, godi.Group("validators"))
With Interface Registration
// Register concrete types as interfaces in a group
services.AddSingleton(NewCSVExporter, godi.Group("exporters"), godi.As[Exporter]())
services.AddSingleton(NewJSONExporter, godi.Group("exporters"), godi.As[Exporter]())
services.AddSingleton(NewXMLExporter, godi.Group("exporters"), godi.As[Exporter]())
// Resolve all exporters
exporters := godi.MustResolveGroup[Exporter](provider, "exporters")
Common Use Cases
Middleware Pipeline
type Middleware interface {
Process(next http.Handler) http.Handler
}
// Register middleware
services.AddScoped(NewAuthMiddleware, godi.Group("middleware"))
services.AddScoped(NewRateLimitMiddleware, godi.Group("middleware"))
services.AddScoped(NewLoggingMiddleware, godi.Group("middleware"))
services.AddScoped(NewMetricsMiddleware, godi.Group("middleware"))
// Build middleware pipeline
func BuildPipeline(provider godi.Provider) http.Handler {
middlewares := godi.MustResolveGroup[Middleware](provider, "middleware")
handler := http.HandlerFunc(finalHandler)
// Apply middleware in reverse order
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i].Process(handler)
}
return handler
}
Plugin System
type Plugin interface {
Name() string
Initialize() error
Execute() error
}
// Register plugins
services.AddSingleton(NewDatabasePlugin, godi.Group("plugins"), godi.As[Plugin]())
services.AddSingleton(NewCachePlugin, godi.Group("plugins"), godi.As[Plugin]())
services.AddSingleton(NewMonitoringPlugin, godi.Group("plugins"), godi.As[Plugin]())
// Plugin manager
type PluginManager struct {
plugins []Plugin
}
func NewPluginManager(provider godi.Provider) *PluginManager {
return &PluginManager{
plugins: godi.MustResolveGroup[Plugin](provider, "plugins"),
}
}
func (m *PluginManager) InitializeAll() error {
for _, plugin := range m.plugins {
if err := plugin.Initialize(); err != nil {
return fmt.Errorf("failed to initialize %s: %w",
plugin.Name(), err)
}
}
return nil
}
Event Handlers
type EventHandler interface {
CanHandle(event Event) bool
Handle(event Event) error
}
// Register event handlers
services.AddSingleton(NewUserCreatedHandler, godi.Group("event-handlers"), godi.As[EventHandler]())
services.AddSingleton(NewOrderPlacedHandler, godi.Group("event-handlers"), godi.As[EventHandler]())
services.AddSingleton(NewPaymentProcessedHandler, godi.Group("event-handlers"), godi.As[EventHandler]())
// Event dispatcher
type EventDispatcher struct {
handlers []EventHandler
}
func NewEventDispatcher(provider godi.Provider) *EventDispatcher {
return &EventDispatcher{
handlers: godi.MustResolveGroup[EventHandler](provider, "event-handlers"),
}
}
func (d *EventDispatcher) Dispatch(event Event) error {
for _, handler := range d.handlers {
if handler.CanHandle(event) {
if err := handler.Handle(event); err != nil {
return err
}
}
}
return nil
}
Validation Pipeline
type Validator interface {
Validate(data any) []ValidationError
}
// Register validators
services.AddTransient(NewRequiredFieldsValidator, godi.Group("validators"), godi.As[Validator]())
services.AddTransient(NewEmailFormatValidator, godi.Group("validators"), godi.As[Validator]())
services.AddTransient(NewBusinessRulesValidator, godi.Group("validators"), godi.As[Validator]())
// Validation service
func ValidateData(provider godi.Provider, data any) []ValidationError {
validators := godi.MustResolveGroup[Validator](provider, "validators")
var errors []ValidationError
for _, validator := range validators {
errors = append(errors, validator.Validate(data)...)
}
return errors
}
Advanced Patterns
Ordered Groups
type OrderedService interface {
Order() int
Execute() error
}
// Services with order
type FirstService struct{}
func (s *FirstService) Order() int { return 1 }
func (s *FirstService) Execute() error { return nil }
type SecondService struct{}
func (s *SecondService) Order() int { return 2 }
func (s *SecondService) Execute() error { return nil }
// Register in group
services.AddSingleton(NewFirstService, godi.Group("ordered"), godi.As[OrderedService]())
services.AddSingleton(NewSecondService, godi.Group("ordered"), godi.As[OrderedService]())
// Execute in order
func ExecuteOrdered(provider godi.Provider) error {
services := godi.MustResolveGroup[OrderedService](provider, "ordered")
// Sort by order
sort.Slice(services, func(i, j int) bool {
return services[i].Order() < services[j].Order()
})
// Execute in order
for _, svc := range services {
if err := svc.Execute(); err != nil {
return err
}
}
return nil
}
Conditional Group Members
func RegisterExporters(services godi.Collection, config *Config) {
// Always register CSV
services.AddSingleton(NewCSVExporter, godi.Group("exporters"), godi.As[Exporter]())
// Conditionally register others
if config.EnableJSON {
services.AddSingleton(NewJSONExporter, godi.Group("exporters"), godi.As[Exporter]())
}
if config.EnableXML {
services.AddSingleton(NewXMLExporter, godi.Group("exporters"), godi.As[Exporter]())
}
if config.EnableExcel {
services.AddSingleton(NewExcelExporter, godi.Group("exporters"), godi.As[Exporter]())
}
}
Dynamic Group Resolution
type ServiceRegistry struct {
provider godi.Provider
groups map[string]string
}
func (r *ServiceRegistry) GetServices(category string) []Service {
groupName, ok := r.groups[category]
if !ok {
return nil
}
services, err := godi.ResolveGroup[Service](r.provider, groupName)
if err != nil {
return nil
}
return services
}
With Parameter Objects
Group Dependencies in Parameter Objects
type ApplicationParams struct {
godi.In
Middlewares []Middleware `group:"middleware"`
Validators []Validator `group:"validators"`
Handlers []EventHandler `group:"event-handlers"`
}
func NewApplication(params ApplicationParams) *Application {
return &Application{
middlewares: params.Middlewares,
validators: params.Validators,
handlers: params.Handlers,
}
}
// godi automatically injects all group members
services.AddSingleton(NewApplication)
Testing with Groups
Test Specific Group Members
func TestEventHandlers(t *testing.T) {
services := godi.NewCollection()
// Register test handlers
services.AddSingleton(NewMockUserHandler, godi.Group("handlers"), godi.As[EventHandler]())
services.AddSingleton(NewMockOrderHandler, godi.Group("handlers"), godi.As[EventHandler]())
provider, err := services.Build()
assert.NoError(t, err)
handlers := godi.MustResolveGroup[EventHandler](provider, "handlers")
assert.Len(t, handlers, 2)
// Test each handler
for _, handler := range handlers {
err := handler.Handle(testEvent)
assert.NoError(t, err)
}
}
Mock Entire Groups
func SetupTestProvider() godi.Provider {
services := godi.NewCollection()
// Register mock group
for i := 0; i < 3; i++ {
services.AddSingleton(
func(id int) func() Service {
return func() Service {
return &MockService{ID: id}
}
}(i),
godi.Group("services"),
)
}
provider, _ := services.Build()
return provider
}
Best Practices
Use consistent naming - Group names should be plural and descriptive
Document group members - List what belongs in each group
Handle empty groups - Check if group is empty before using
Consider ordering - Some groups may need ordered execution
Keep groups focused - Don’t mix unrelated services
Group Constants
const (
MiddlewareGroup = "middleware"
ValidatorsGroup = "validators"
HandlersGroup = "handlers"
ExportersGroup = "exporters"
)
// Use constants
services.AddScoped(NewAuthMiddleware, godi.Group(MiddlewareGroup))
middlewares := godi.MustResolveGroup[Middleware](provider, MiddlewareGroup)
Empty Group Handling
func ProcessWithHandlers(provider godi.Provider, data any) error {
handlers, err := godi.ResolveGroup[Handler](provider, "handlers")
if err != nil {
return err
}
if len(handlers) == 0 {
return errors.New("no handlers registered")
}
for _, handler := range handlers {
if err := handler.Process(data); err != nil {
return err
}
}
return nil
}
Common Pitfalls
Mixing Keys and Groups
// ❌ Can't use both Name and Group
services.AddSingleton(NewService, godi.Name("service1"), godi.Group("services")) // Error!
// ✅ Use one or the other
services.AddSingleton(NewService1, godi.Group("services"))
services.AddSingleton(NewService2, godi.Name("special"))
Type Consistency
// ❌ Mixed types in group
services.AddSingleton(NewLogger, godi.Group("services"))
services.AddSingleton(NewDatabase, godi.Group("services"))
// Can't resolve as single type!
// ✅ Same interface for all group members
services.AddSingleton(NewLogger, godi.Group("services"), godi.As[Service]())
services.AddSingleton(NewDatabase, godi.Group("services"), godi.As[Service]())
Next Steps
Learn about Parameter Objects
Explore Result Objects
Understand Interface Registration