Skip to content

Commit 8215292

Browse files
committed
refactor: streamline dependency injection with Provide and update FARP configuration
- Replaced RegisterSingleton with Provide for improved dependency injection consistency across services. - Updated FARP configuration options in the discovery extension to enhance service registration and metadata handling. - Removed deprecated methods and streamlined the DI helpers for better clarity and maintainability. - Enhanced documentation to reflect changes in FARP and service discovery features. These updates improve the overall architecture and usability of the dependency injection and discovery systems.
1 parent 33d74ab commit 8215292

99 files changed

Lines changed: 872 additions & 401 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app_impl.go

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -265,52 +265,28 @@ func newApp(config AppConfig) *app {
265265

266266
// Key-based registration (backward compatibility)
267267
// Register with both the full keys and simple keys for vessel compatibility
268-
_ = RegisterSingleton(container, shared.LoggerKey, func(c Container) (Logger, error) {
268+
_ = Provide(container, func() (Logger, error) {
269269
return logger, nil
270-
})
271-
_ = RegisterSingleton(container, "logger", func(c Container) (Logger, error) {
272-
return logger, nil
273-
}) // Simple key for vessel.GetLogger
270+
}, vessel.WithAliases(shared.LoggerKey))
274271

275-
_ = RegisterSingleton(container, shared.ConfigKey, func(c Container) (ConfigManager, error) {
272+
_ = Provide(container, func() (ConfigManager, error) {
276273
return configManager, nil
277-
})
274+
}, vessel.WithAliases(shared.ConfigKey))
278275

279-
_ = RegisterSingleton(container, shared.MetricsKey, func(c Container) (Metrics, error) {
276+
_ = Provide(container, func() (Metrics, error) {
280277
return metrics, nil
281-
})
282-
_ = RegisterSingleton(container, "metrics", func(c Container) (Metrics, error) {
283-
return metrics, nil
284-
}) // Simple key for vessel.GetMetrics
278+
}, vessel.WithAliases(shared.MetricsKey))
285279

286-
_ = RegisterSingleton(container, shared.HealthManagerKey, func(c Container) (HealthManager, error) {
280+
_ = Provide(container, func() (HealthManager, error) {
287281
return healthManager, nil
288-
})
282+
}, vessel.WithAliases(shared.HealthManagerKey))
289283

290-
_ = RegisterSingleton(container, shared.RouterKey, func(c Container) (Router, error) {
284+
_ = Provide(container, func() (Router, error) {
291285
return router, nil
292-
})
293-
294-
// Type-based constructor registration (new pattern for constructor injection)
295-
// These allow services to be resolved by type without string keys
296-
_ = ProvideConstructor(container, func() Logger {
297-
return logger
298-
})
299-
_ = ProvideConstructor(container, func() ConfigManager {
300-
return configManager
301-
})
302-
_ = ProvideConstructor(container, func() Metrics {
303-
return metrics
304-
})
305-
_ = ProvideConstructor(container, func() HealthManager {
306-
return healthManager
307-
})
308-
_ = ProvideConstructor(container, func() Router {
309-
return router
310-
})
286+
}, vessel.WithAliases(shared.RouterKey))
311287

312288
// Create lifecycle manager
313-
lifecycleManager := NewLifecycleManager(logger)
289+
lm := NewLifecycleManager(logger)
314290

315291
a := &app{
316292
config: config,
@@ -320,7 +296,7 @@ func newApp(config AppConfig) *app {
320296
logger: logger,
321297
metrics: metrics,
322298
healthManager: healthManager,
323-
lifecycleManager: lifecycleManager,
299+
lifecycleManager: lm,
324300
extensions: []Extension{}, // Initialize empty, will populate below
325301
startTime: time.Now(),
326302
}

cmd/forge/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ require (
8989
github.com/xdg-go/stringprep v1.0.4 // indirect
9090
github.com/xraph/confy v0.0.3 // indirect
9191
github.com/xraph/go-utils v0.0.11 // indirect
92-
github.com/xraph/vessel v0.0.3 // indirect
92+
github.com/xraph/vessel v0.0.4 // indirect
9393
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
9494
go.mongodb.org/mongo-driver v1.17.4 // indirect
9595
go.uber.org/multierr v1.11.0 // indirect

cmd/forge/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,8 @@ github.com/xraph/confy v0.0.3 h1:IaTxV0wyQx61amDFdK/tJwfQDGT1z3lHRL8V+NDZxjU=
306306
github.com/xraph/confy v0.0.3/go.mod h1:/uhVfKibPR+kn7MI9LkVVekk84NP0sxsKZ9sFQoQ5Kc=
307307
github.com/xraph/go-utils v0.0.11 h1:9CT0l4yQ8RWIQJmFQcxjA1cb8Re2fHxJPDNmdmG8Vz8=
308308
github.com/xraph/go-utils v0.0.11/go.mod h1:yp+PD9dXSA7tA9Pxmuveg5E7Ht1iHIVov8yMvanMG7U=
309-
github.com/xraph/vessel v0.0.3 h1:h1ECL4ASRTbtI6diJ6MYM6gW5UTmIbq+lqs4c4tlqbY=
310-
github.com/xraph/vessel v0.0.3/go.mod h1:e1TQfzvBcAvCPdfYC0+9VoOW5SrKX07VNdaimENU8B0=
309+
github.com/xraph/vessel v0.0.4 h1:PEwZH3ApajHlGWVvx9tjIJ7Wkfm4QU9Dy69Qh8Dxa1s=
310+
github.com/xraph/vessel v0.0.4/go.mod h1:t7UbkGensTnqpvda2gOnH2/lnAwnUpa5oEDFHwIqQ+k=
311311
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
312312
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
313313
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

di.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
// Container provides dependency injection with lifecycle management.
88
type Container = vessel.Vessel
99

10+
// ProvideOption is an alias for vessel.ConstructorOption, used to configure options for constructing objects.
11+
type ProvideOption = vessel.ConstructorOption
12+
1013
// Scope represents a lifetime scope for scoped services
1114
// Typically used for HTTP requests or other bounded operations.
1215
type Scope = vessel.Scope

di_helpers.go

Lines changed: 36 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,21 @@
11
package forge
22

33
import (
4-
"context"
5-
64
"github.com/xraph/forge/internal/health"
75
"github.com/xraph/forge/internal/shared"
86
"github.com/xraph/vessel"
97
)
108

11-
// Resolve with type safety.
12-
func Resolve[T any](c Container, name string) (T, error) {
13-
return vessel.Resolve[T](c, name)
14-
}
15-
16-
// Must resolves or panics - use only during startup.
17-
func Must[T any](c Container, name string) T {
18-
return vessel.Must[T](c, name)
19-
}
20-
21-
// ResolveReady resolves a service with type safety, ensuring it and its dependencies are started first.
22-
// This is useful during extension Register() phase when you need a dependency
23-
// to be fully initialized before use.
24-
//
25-
// Example usage in an extension's Register() method:
26-
//
27-
// func (e *MyExtension) Register(app forge.App) error {
28-
// ctx := context.Background()
29-
// dbManager, err := forge.ResolveReady[*database.DatabaseManager](ctx, app.Container(), database.ManagerKey)
30-
// if err != nil {
31-
// return fmt.Errorf("database required: %w", err)
32-
// }
33-
// // dbManager is now fully started with open connections
34-
// e.redis, _ = dbManager.Redis("cache")
35-
// return nil
36-
// }
37-
func ResolveReady[T any](ctx context.Context, c Container, name string) (T, error) {
38-
return vessel.ResolveReady[T](ctx, c, name)
39-
}
40-
41-
// MustResolveReady resolves or panics, ensuring the service is started first.
42-
// Use only during startup/registration phase.
43-
func MustResolveReady[T any](ctx context.Context, c Container, name string) T {
44-
return vessel.MustResolveReady[T](ctx, c, name)
45-
}
46-
47-
// RegisterSingleton is a convenience wrapper for singleton services.
48-
func RegisterSingleton[T any](c Container, name string, factory func(Container) (T, error)) error {
49-
return vessel.RegisterSingleton[T](c, name, factory)
50-
}
51-
52-
// RegisterTransient is a convenience wrapper for transient services.
53-
func RegisterTransient[T any](c Container, name string, factory func(Container) (T, error)) error {
54-
return vessel.RegisterTransient[T](c, name, factory)
55-
}
56-
57-
// RegisterScoped is a convenience wrapper for request-scoped services.
58-
func RegisterScoped[T any](c Container, name string, factory func(Container) (T, error)) error {
59-
return vessel.RegisterScoped[T](c, name, factory)
60-
}
61-
62-
// RegisterSingletonWith registers a singleton service with typed dependency injection.
63-
// Accepts InjectOption arguments followed by a factory function.
64-
//
65-
// Usage:
66-
//
67-
// forge.RegisterSingletonWith[*UserService](c, "userService",
68-
// forge.Inject[*bun.DB]("database"),
69-
// func(db *bun.DB) (*UserService, error) {
70-
// return &UserService{db: db}, nil
71-
// },
72-
// )
73-
func RegisterSingletonWith[T any](c Container, name string, args ...any) error {
74-
return vessel.RegisterSingletonWith[T](c, name, args...)
75-
}
76-
77-
// RegisterTransientWith registers a transient service with typed dependency injection.
78-
// Accepts InjectOption arguments followed by a factory function.
79-
//
80-
// Usage:
81-
//
82-
// forge.RegisterTransientWith[*Request](c, "request",
83-
// forge.Inject[*Context]("ctx"),
84-
// func(ctx *Context) (*Request, error) {
85-
// return &Request{ctx: ctx}, nil
86-
// },
87-
// )
88-
func RegisterTransientWith[T any](c Container, name string, args ...any) error {
89-
return vessel.RegisterTransientWith[T](c, name, args...)
90-
}
91-
92-
// RegisterScopedWith registers a scoped service with typed dependency injection.
93-
// Accepts InjectOption arguments followed by a factory function.
94-
//
95-
// Usage:
96-
//
97-
// forge.RegisterScopedWith[*Session](c, "session",
98-
// forge.Inject[*User]("user"),
99-
// func(user *User) (*Session, error) {
100-
// return &Session{user: user}, nil
101-
// },
102-
// )
103-
func RegisterScopedWith[T any](c Container, name string, args ...any) error {
104-
return vessel.RegisterScopedWith[T](c, name, args...)
105-
}
106-
107-
// RegisterInterface registers an implementation as an interface
108-
// Supports all lifecycle options (Singleton, Scoped, Transient).
109-
func RegisterInterface[I, T any](c Container, name string, factory func(Container) (T, error), opts ...RegisterOption) error {
110-
return vessel.RegisterInterface[I, T](c, name, factory, opts...)
111-
}
112-
113-
// RegisterValue registers a pre-built instance (always singleton).
114-
func RegisterValue[T any](c Container, name string, instance T) error {
115-
return vessel.RegisterValue[T](c, name, instance)
116-
}
117-
118-
// RegisterSingletonInterface is a convenience wrapper.
119-
func RegisterSingletonInterface[I, T any](c Container, name string, factory func(Container) (T, error)) error {
120-
return vessel.RegisterSingletonInterface[I, T](c, name, factory)
121-
}
122-
123-
// RegisterScopedInterface is a convenience wrapper.
124-
func RegisterScopedInterface[I, T any](c Container, name string, factory func(Container) (T, error)) error {
125-
return vessel.RegisterScopedInterface[I, T](c, name, factory)
126-
}
127-
128-
// RegisterTransientInterface is a convenience wrapper.
129-
func RegisterTransientInterface[I, T any](c Container, name string, factory func(Container) (T, error)) error {
130-
return vessel.RegisterTransientInterface[I, T](c, name, factory)
131-
}
132-
133-
// ResolveScope is a helper for resolving from a scope.
134-
func ResolveScope[T any](s Scope, name string) (T, error) {
135-
return vessel.ResolveScope[T](s, name)
136-
}
137-
138-
// MustScope resolves from scope or panics.
139-
func MustScope[T any](s Scope, name string) T {
140-
return vessel.MustScope[T](s, name)
141-
}
142-
1439
// GetLogger resolves the logger from the container
14410
// Returns the logger instance and an error if resolution fails.
14511
func GetLogger(c Container) (Logger, error) {
146-
return vessel.GetLogger(c)
12+
return vessel.Inject[Logger](c)
14713
}
14814

14915
// GetMetrics resolves the metrics from the container
15016
// Returns the metrics instance and an error if resolution fails.
15117
func GetMetrics(c Container) (Metrics, error) {
152-
return vessel.GetMetrics(c)
18+
return vessel.Inject[Metrics](c)
15319
}
15420

15521
// GetHealthManager resolves the health manager from the container
@@ -222,14 +88,6 @@ type OptionalLazyRef[T any] = vessel.OptionalLazy[T]
22288
// This is useful for transient dependencies where a fresh instance is needed each time.
22389
type ProviderRef[T any] = vessel.Provider[T]
22490

225-
// LazyAny is a non-generic lazy wrapper used with LazyInject.
226-
// Use this type in your factory function when using LazyInject[T].
227-
type LazyAny = vessel.LazyAny
228-
229-
// OptionalLazyAny is a non-generic optional lazy wrapper used with LazyOptionalInject.
230-
// Use this type in your factory function when using LazyOptionalInject[T].
231-
type OptionalLazyAny = vessel.OptionalLazyAny
232-
23391
// NewLazyRef creates a new lazy dependency wrapper.
23492
func NewLazyRef[T any](c Container, name string) *LazyRef[T] {
23593
return vessel.NewLazy[T](c, name)
@@ -245,13 +103,6 @@ func NewProviderRef[T any](c Container, name string) *ProviderRef[T] {
245103
return vessel.NewProvider[T](c, name)
246104
}
247105

248-
// =============================================================================
249-
// Typed Injection Helpers
250-
// =============================================================================
251-
252-
// InjectOption represents a dependency injection option with type information.
253-
type InjectOption = vessel.InjectOption
254-
255106
// Inject creates an eager injection option for a dependency.
256107
// The dependency is resolved immediately when the service is created.
257108
//
@@ -261,57 +112,47 @@ type InjectOption = vessel.InjectOption
261112
// forge.Inject[*bun.DB]("database"),
262113
// func(db *bun.DB) (*UserService, error) { ... },
263114
// )
264-
func Inject[T any](name string) InjectOption {
265-
return vessel.Inject[T](name)
266-
}
267-
268-
// LazyInject creates a lazy injection option for a dependency.
269-
// The dependency is resolved on first access via Lazy[T].Get().
270-
func LazyInject[T any](name string) InjectOption {
271-
return vessel.LazyInject[T](name)
272-
}
273-
274-
// OptionalInject creates an optional injection option for a dependency.
275-
// The dependency is resolved immediately but returns nil if not found.
276-
func OptionalInject[T any](name string) InjectOption {
277-
return vessel.OptionalInject[T](name)
115+
func Inject[T any](c Container) (T, error) {
116+
return vessel.Inject[T](c)
278117
}
279118

280-
// LazyOptionalInject creates a lazy optional injection option.
281-
// The dependency is resolved on first access and returns nil if not found.
282-
func LazyOptionalInject[T any](name string) InjectOption {
283-
return vessel.LazyOptionalInject[T](name)
284-
}
285-
286-
// ProviderInject creates an injection option for a transient dependency provider.
287-
func ProviderInject[T any](name string) InjectOption {
288-
return vessel.ProviderInject[T](name)
289-
}
290-
291-
// Provide registers a service with typed dependency injection.
292-
// It accepts InjectOption arguments followed by a factory function.
119+
// Provide registers a constructor function with automatic dependency resolution.
120+
// Dependencies are inferred from function parameters and all return types (except error)
121+
// are registered as services.
293122
//
294-
// The factory function receives the resolved dependencies in order and returns
295-
// the service instance and an optional error.
123+
// This follows the Uber dig pattern for constructor-based dependency injection:
124+
// - Function parameters become dependencies (resolved by type)
125+
// - Return types become provided services
126+
// - Error return type is handled for construction failures
296127
//
297-
// Usage:
128+
// Example:
298129
//
299-
// forge.Provide[*UserService](c, "userService",
300-
// forge.Inject[*bun.DB]("database"),
301-
// forge.Inject[Logger]("logger"),
302-
// forge.LazyInject[*Cache]("cache"),
303-
// func(db *bun.DB, logger Logger, cache *forge.LazyRef[*Cache]) (*UserService, error) {
304-
// return &UserService{db, logger, cache}, nil
305-
// },
306-
// )
307-
func Provide[T any](c Container, name string, args ...any) error {
308-
return vessel.Provide[T](c, name, args...)
130+
// // Simple constructor
131+
// func NewUserService(db *Database, logger *Logger) *UserService {
132+
// return &UserService{db: db, logger: logger}
133+
// }
134+
// Provide(c, NewUserService)
135+
//
136+
// // Constructor with error
137+
// func NewDatabase(config *Config) (*Database, error) {
138+
// return sql.Open(config.Driver, config.DSN)
139+
// }
140+
// Provide(c, NewDatabase)
141+
//
142+
// // Using In struct for many dependencies
143+
// type ServiceParams struct {
144+
// vessel.In
145+
// DB *Database
146+
// Logger *Logger `optional:"true"`
147+
// }
148+
// func NewService(p ServiceParams) *Service {
149+
// return &Service{db: p.DB, logger: p.Logger}
150+
// }
151+
// Provide(c, NewService)
152+
func Provide(c Container, constructor any, opts ...ProvideOption) error {
153+
return vessel.Provide(c, constructor, opts...)
309154
}
310155

311-
// ProvideWithOpts is like Provide but accepts additional RegisterOptions.
312-
func ProvideWithOpts[T any](c Container, name string, opts []RegisterOption, args ...any) error {
313-
return vessel.ProvideWithOpts[T](c, name, opts, args...)
314-
}
315156

316157
// =============================================================================
317158
// Constructor Injection (Type-Based DI)
@@ -348,16 +189,6 @@ func InjectType[T any](c Container) (T, error) {
348189
return vessel.InjectType[T](c)
349190
}
350191

351-
// MustInjectType resolves a service by type or panics.
352-
// Use only during application startup where panics are acceptable.
353-
//
354-
// Usage:
355-
//
356-
// db := forge.MustInjectType[*Database](c)
357-
func MustInjectType[T any](c Container) T {
358-
return vessel.MustInjectType[T](c)
359-
}
360-
361192
// InjectNamed resolves a named service by type.
362193
// Used when you have multiple instances of the same type.
363194
//

0 commit comments

Comments
 (0)