Skip to content

Feature Request: Generic Service/Dependency Injection Support #2601

@livebee

Description

@livebee

Summary

Add support for injecting custom services and dependencies into GoFr's container, enabling enterprise patterns like Repository, Service Layer, and ORM integration.

Motivation

Currently, GoFr's Container has hardcoded fields for each datasource (Redis, Mongo, Cassandra, etc.), but lacks a mechanism for:

  1. Custom Business Services - UserService, OrderService, PaymentGateway
  2. ORM Integration - GORM, ent, sqlx (many enterprises use ORMs)
  3. Repository Pattern - Injecting repository instances
  4. Multi-instance Databases - Multiple Redis/SQL connections with different names

Current Limitation

// Container struct (current)
type Container struct {
    Redis     Redis           // Single instance only
    SQL       DB
    Mongo     Mongo
    // ... 15+ hardcoded fields
    // ❌ No place for custom services
}

Real-World Use Case

We're exploring GoFr for our multi-tenant SaaS application. Our architecture uses:

  • GORM for database operations (with Preload, Scopes, Transactions)
  • TenantBaseRepository[T] with L1+L2 caching
  • Multiple business services (UserService, BillingService, etc.)

Currently, there's no clean way to inject these into GoFr handlers.

Proposed Solution

  1. Add Generic Service Registry
type Container struct {
    // ... existing fields ...

    // NEW: Generic service registry (thread-safe)
    services sync.Map  // map[string]any
}

// Generic methods
func (c *Container) SetService(name string, svc any)
func (c *Container) GetService(name string) (any, bool)

// Type-safe generic helper (Go 1.18+)
func GetService[T any](c *Container, name string) (T, bool)
  1. Add App-Level API
// app.go
func (a *App) AddService(name string, svc any) {
    a.container.SetService(name, svc)
}
  1. Usage Example
func main() {
    app := gofr.New()

    // Inject GORM
    gormDB, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    app.AddService("gorm", gormDB)

    // Inject custom repository
    userRepo := repository.NewUserRepository(gormDB)
    app.AddService("userRepo", userRepo)

    app.GET("/users/{id}", GetUser)
    app.Run()
}

func GetUser(ctx *gofr.Context) (any, error) {
    repo, ok := ctx.GetService[*UserRepository](ctx.Container, "userRepo")
    if !ok {
        return nil, http.ErrorMissingParam{Params: []string{"userRepo"}}
    }

    return repo.GetByID(ctx, ctx.PathParam("id"))
}

Benefits

Benefit Description
Extensibility Users can inject any service without framework changes
ORM Support GORM, ent, sqlx integration becomes trivial
Testability Easy mock injection for unit tests
Enterprise Ready Supports layered architecture patterns
Backward Compatible Existing APIs unchanged

Alternatives Considered

  1. Using global variables - Anti-pattern, not testable
  2. Closure injection - Works but verbose, doesn't scale
  3. Separate DI container (wire, dig) - Extra complexity, doesn't integrate with GoFr context

Additional Context

Related: GORM as First-Class Datasource

While generic DI solves the immediate need, many Go projects use GORM. A future enhancement could add native GORM support with:

  • Auto health-check integration
  • Query metrics/traces
  • Migration support

This could be a separate issue/PR once generic DI is available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageThe issue needs triaging.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions