Last active
July 5, 2025 18:52
-
-
Save emad-elsaid/7cef474037dd3f5afbdc0cce87300180 to your computer and use it in GitHub Desktop.
Dependency injection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"database/sql" | |
"fmt" | |
"log/slog" | |
"reflect" | |
"sync" | |
) | |
func main() { | |
app := new(App) | |
RegisterResource(app, "db", NewDB) | |
// Get it later | |
db, err := GetResource[*sql.DB](app, "db") | |
if err != nil { | |
slog.Error("Failed to get resource", "error", err) | |
return | |
} | |
slog.Info("Hello, World!", "db", db) | |
} | |
// App contains all application wide instances and shared resources | |
type App struct { | |
// resources is a map from the type of resource -> a map from resource instance ID to its factory | |
// So every resource type can have multiple instances registered in runtime and retrieved when needed | |
// for example a DBPool factory. Can be registered twice once for master DB and another for readonly DB | |
resources sync.Map | |
} | |
// Register the factory of this type with the given name. it will be executed | |
// only once and the return instance/error will be reused (uses sync.Once) | |
func RegisterResource[T any](app *App, id string, factory func() (T, error)) { | |
var t T | |
fs, _ := app.resources.LoadOrStore(reflect.TypeOf(t), new(sync.Map)) | |
factories := fs.(*sync.Map) | |
once := sync.OnceValues(factory) | |
factories.Store(id, once) | |
} | |
func GetResource[T any](app *App, id string) (T, error) { | |
var t T | |
fs, ok := app.resources.Load(reflect.TypeOf(t)) | |
if !ok { | |
return *new(T), fmt.Errorf("Resource not found: %s", id) | |
} | |
factories := fs.(*sync.Map) | |
f, ok := factories.Load(id) | |
if !ok { | |
return *new(T), fmt.Errorf("Factory not found: %s", id) | |
} | |
factory, ok := f.(func() (T, error)) | |
if !ok { | |
return *new(T), fmt.Errorf("Failed to get cast resource factory: %s", id) | |
} | |
return factory() | |
} | |
func NewDB() (*sql.DB, error) { | |
// init new db here | |
return &sql.DB{}, nil | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
"testing" | |
"github.com/stretchr/testify/require" | |
) | |
// Test types for dependency injection | |
type TestService struct { | |
Name string | |
} | |
type AnotherService struct { | |
ID int | |
} | |
type Config struct { | |
Value string | |
} | |
func TestRegisterResource(t *testing.T) { | |
t.Run("should register resource factory successfully", func(t *testing.T) { | |
app := &App{} | |
factory := func() (TestService, error) { | |
return TestService{Name: "test"}, nil | |
} | |
RegisterResource(app, "test-service", factory) | |
// Verify the resource was registered by trying to get it | |
result, err := GetResource[TestService](app, "test-service") | |
require.NoError(t, err) | |
require.Equal(t, "test", result.Name) | |
}) | |
t.Run("should register multiple resources of same type", func(t *testing.T) { | |
app := &App{} | |
factory1 := func() (TestService, error) { | |
return TestService{Name: "service1"}, nil | |
} | |
factory2 := func() (TestService, error) { | |
return TestService{Name: "service2"}, nil | |
} | |
RegisterResource(app, "service1", factory1) | |
RegisterResource(app, "service2", factory2) | |
// Verify both resources were registered | |
result1, err := GetResource[TestService](app, "service1") | |
require.NoError(t, err) | |
require.Equal(t, "service1", result1.Name) | |
result2, err := GetResource[TestService](app, "service2") | |
require.NoError(t, err) | |
require.Equal(t, "service2", result2.Name) | |
}) | |
t.Run("should register different resource types", func(t *testing.T) { | |
app := &App{} | |
testFactory := func() (TestService, error) { | |
return TestService{Name: "test"}, nil | |
} | |
anotherFactory := func() (AnotherService, error) { | |
return AnotherService{ID: 123}, nil | |
} | |
configFactory := func() (Config, error) { | |
return Config{Value: "config"}, nil | |
} | |
RegisterResource(app, "test-service", testFactory) | |
RegisterResource(app, "another-service", anotherFactory) | |
RegisterResource(app, "config", configFactory) | |
// Verify all resources were registered | |
testResult, err := GetResource[TestService](app, "test-service") | |
require.NoError(t, err) | |
require.Equal(t, "test", testResult.Name) | |
anotherResult, err := GetResource[AnotherService](app, "another-service") | |
require.NoError(t, err) | |
require.Equal(t, 123, anotherResult.ID) | |
configResult, err := GetResource[Config](app, "config") | |
require.NoError(t, err) | |
require.Equal(t, "config", configResult.Value) | |
}) | |
} | |
func TestGetResource(t *testing.T) { | |
t.Run("should get registered resource successfully", func(t *testing.T) { | |
app := &App{} | |
expectedService := TestService{Name: "test-service"} | |
factory := func() (TestService, error) { | |
return expectedService, nil | |
} | |
RegisterResource(app, "test-service", factory) | |
result, err := GetResource[TestService](app, "test-service") | |
require.NoError(t, err) | |
require.Equal(t, expectedService, result) | |
}) | |
t.Run("should return error when resource type not found", func(t *testing.T) { | |
app := &App{} | |
// Try to get a resource type that was never registered | |
result, err := GetResource[TestService](app, "non-existent") | |
require.Error(t, err) | |
require.Contains(t, err.Error(), "Resource not found: non-existent") | |
require.Equal(t, TestService{}, result) | |
}) | |
t.Run("should return error when factory not found for type", func(t *testing.T) { | |
app := &App{} | |
// Register a different type first | |
anotherFactory := func() (AnotherService, error) { | |
return AnotherService{ID: 123}, nil | |
} | |
RegisterResource(app, "another-service", anotherFactory) | |
// Try to get TestService with name that doesn't exist for this type | |
result, err := GetResource[TestService](app, "non-existent") | |
require.Error(t, err) | |
require.Contains(t, err.Error(), "Resource not found: non-existent") | |
require.Equal(t, TestService{}, result) | |
}) | |
t.Run("should return error when factory name not found", func(t *testing.T) { | |
app := &App{} | |
factory := func() (TestService, error) { | |
return TestService{Name: "test"}, nil | |
} | |
RegisterResource(app, "test-service", factory) | |
// Try to get with wrong name | |
result, err := GetResource[TestService](app, "wrong-name") | |
require.Error(t, err) | |
require.Contains(t, err.Error(), "Factory not found: wrong-name") | |
require.Equal(t, TestService{}, result) | |
}) | |
t.Run("should handle factory that returns error", func(t *testing.T) { | |
app := &App{} | |
expectedError := errors.New("factory error") | |
factory := func() (TestService, error) { | |
return TestService{}, expectedError | |
} | |
RegisterResource(app, "error-service", factory) | |
result, err := GetResource[TestService](app, "error-service") | |
require.Error(t, err) | |
require.Equal(t, expectedError, err) | |
require.Equal(t, TestService{}, result) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment