-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
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:
- Custom Business Services - UserService, OrderService, PaymentGateway
- ORM Integration - GORM, ent, sqlx (many enterprises use ORMs)
- Repository Pattern - Injecting repository instances
- 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
- 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)- Add App-Level API
// app.go
func (a *App) AddService(name string, svc any) {
a.container.SetService(name, svc)
}- 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
- Using global variables - Anti-pattern, not testable
- Closure injection - Works but verbose, doesn't scale
- 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.